sql-jarvis 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/CHANGELOG.md +228 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +775 -0
  7. data/Rakefile +1 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  11. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  12. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  13. data/app/assets/javascripts/blazer/Chart.js +14145 -0
  14. data/app/assets/javascripts/blazer/Sortable.js +1144 -0
  15. data/app/assets/javascripts/blazer/ace.js +6 -0
  16. data/app/assets/javascripts/blazer/ace/ace.js +11 -0
  17. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
  18. data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
  19. data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
  20. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
  21. data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
  22. data/app/assets/javascripts/blazer/application.js +79 -0
  23. data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
  24. data/app/assets/javascripts/blazer/chartkick.js +1693 -0
  25. data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
  26. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  27. data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10308 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
  30. data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
  31. data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
  32. data/app/assets/javascripts/blazer/moment.js +3043 -0
  33. data/app/assets/javascripts/blazer/queries.js +110 -0
  34. data/app/assets/javascripts/blazer/routes.js +23 -0
  35. data/app/assets/javascripts/blazer/selectize.js +3667 -0
  36. data/app/assets/javascripts/blazer/stupidtable.js +114 -0
  37. data/app/assets/javascripts/blazer/vue.js +7515 -0
  38. data/app/assets/stylesheets/blazer/application.css +198 -0
  39. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
  40. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
  41. data/app/assets/stylesheets/blazer/github.css +125 -0
  42. data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
  43. data/app/controllers/blazer/base_controller.rb +103 -0
  44. data/app/controllers/blazer/checks_controller.rb +56 -0
  45. data/app/controllers/blazer/dashboards_controller.rb +105 -0
  46. data/app/controllers/blazer/queries_controller.rb +325 -0
  47. data/app/helpers/blazer/base_helper.rb +57 -0
  48. data/app/mailers/blazer/check_mailer.rb +27 -0
  49. data/app/models/blazer/audit.rb +6 -0
  50. data/app/models/blazer/check.rb +95 -0
  51. data/app/models/blazer/connection.rb +5 -0
  52. data/app/models/blazer/dashboard.rb +13 -0
  53. data/app/models/blazer/dashboard_query.rb +9 -0
  54. data/app/models/blazer/query.rb +31 -0
  55. data/app/models/blazer/record.rb +5 -0
  56. data/app/views/blazer/_nav.html.erb +16 -0
  57. data/app/views/blazer/_variables.html.erb +102 -0
  58. data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
  59. data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
  60. data/app/views/blazer/checks/_form.html.erb +71 -0
  61. data/app/views/blazer/checks/edit.html.erb +1 -0
  62. data/app/views/blazer/checks/index.html.erb +40 -0
  63. data/app/views/blazer/checks/new.html.erb +1 -0
  64. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  65. data/app/views/blazer/dashboards/edit.html.erb +1 -0
  66. data/app/views/blazer/dashboards/new.html.erb +1 -0
  67. data/app/views/blazer/dashboards/show.html.erb +47 -0
  68. data/app/views/blazer/queries/_form.html.erb +240 -0
  69. data/app/views/blazer/queries/edit.html.erb +2 -0
  70. data/app/views/blazer/queries/home.html.erb +152 -0
  71. data/app/views/blazer/queries/new.html.erb +2 -0
  72. data/app/views/blazer/queries/run.html.erb +163 -0
  73. data/app/views/blazer/queries/schema.html.erb +18 -0
  74. data/app/views/blazer/queries/show.html.erb +73 -0
  75. data/app/views/layouts/blazer/application.html.erb +24 -0
  76. data/blazer.gemspec +26 -0
  77. data/config/routes.rb +16 -0
  78. data/lib/blazer.rb +185 -0
  79. data/lib/blazer/adapters/athena_adapter.rb +128 -0
  80. data/lib/blazer/adapters/base_adapter.rb +53 -0
  81. data/lib/blazer/adapters/bigquery_adapter.rb +67 -0
  82. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  83. data/lib/blazer/adapters/elasticsearch_adapter.rb +49 -0
  84. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  85. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  86. data/lib/blazer/adapters/sql_adapter.rb +182 -0
  87. data/lib/blazer/data_source.rb +193 -0
  88. data/lib/blazer/detect_anomalies.R +19 -0
  89. data/lib/blazer/engine.rb +47 -0
  90. data/lib/blazer/result.rb +170 -0
  91. data/lib/blazer/run_statement.rb +40 -0
  92. data/lib/blazer/run_statement_job.rb +21 -0
  93. data/lib/blazer/version.rb +3 -0
  94. data/lib/generators/blazer/install_generator.rb +39 -0
  95. data/lib/generators/blazer/templates/config.yml +62 -0
  96. data/lib/generators/blazer/templates/install.rb +45 -0
  97. data/lib/tasks/blazer.rake +10 -0
  98. metadata +211 -0
@@ -0,0 +1,2 @@
1
+ <% blazer_title "New Query" %>
2
+ <%= render partial: "form" %>
@@ -0,0 +1,163 @@
1
+ <% if @error %>
2
+ <div class="alert alert-danger"><%= @error.first(200) %></div>
3
+ <% elsif !@success %>
4
+ <% if @only_chart %>
5
+ <p class="text-muted">Select variables</p>
6
+ <% else %>
7
+ <div class="alert alert-info">Can’t preview queries with variables...yet!</div>
8
+ <% end %>
9
+ <% else %>
10
+ <% unless @only_chart %>
11
+ <% if @cached_at || @just_cached %>
12
+ <p class="text-muted" style="float: right;">
13
+ <% if @cached_at %>
14
+ Cached <%= time_ago_in_words(@cached_at, include_seconds: true) %> ago
15
+ <% elsif !params[:data_source] %>
16
+ Cached just now
17
+ <% if @data_source.cache_mode == "slow" %>
18
+ (over <%= "%g" % @data_source.cache_slow_threshold %>s)
19
+ <% end %>
20
+ <% end %>
21
+
22
+ <% if @query && !params[:data_source] %>
23
+ <%= link_to "Refresh", refresh_query_path(@query, variable_params), method: :post %>
24
+ <% end %>
25
+ </p>
26
+ <% end %>
27
+ <p class="text-muted">
28
+ <%= pluralize(@rows.size, "row") %>
29
+
30
+ <% @checks.select(&:state).each do |check| %>
31
+ &middot; <small class="check-state <%= check.state.parameterize.gsub("-", "_") %>"><%= link_to check.state.upcase, edit_check_path(check) %></small>
32
+ <% if check.try(:message) %>
33
+ &middot; <%= check.message %>
34
+ <% end %>
35
+ <% end %>
36
+ </p>
37
+ <% end %>
38
+ <% if @rows.any? %>
39
+ <% values = @rows.first %>
40
+ <% chart_id = SecureRandom.hex %>
41
+ <% column_types = @result.column_types %>
42
+ <% chart_type = @result.chart_type %>
43
+ <% chart_options = {id: chart_id, min: nil} %>
44
+ <% series_library = {} %>
45
+ <% target_index = @columns.index { |k| k.downcase == "target" } %>
46
+ <% if target_index %>
47
+ <% series_library[target_index - 1] = {pointStyle: "line", hitRadius: 5, borderColor: "#109618", pointBackgroundColor: "#109618", backgroundColor: "#109618"} %>
48
+ <% end %>
49
+ <% if blazer_maps? && @markers.any? %>
50
+ <div id="map" style="height: <%= @only_chart ? 300 : 500 %>px;"></div>
51
+ <script>
52
+ L.mapbox.accessToken = '<%= Blazer.mapbox_access_token %>';
53
+ var map = L.mapbox.map('map', 'ankane.ioo8nki0');
54
+ <%= blazer_js_var "markers", @markers %>
55
+ var featureLayer = L.mapbox.featureLayer().addTo(map);
56
+ var geojson = [];
57
+ for (var i = 0; i < markers.length; i++) {
58
+ var marker = markers[i];
59
+ geojson.push({
60
+ type: 'Feature',
61
+ geometry: {
62
+ type: 'Point',
63
+ coordinates: [
64
+ marker.longitude,
65
+ marker.latitude
66
+ ]
67
+ },
68
+ properties: {
69
+ description: marker.title,
70
+ 'marker-color': '#f86767',
71
+ 'marker-size': 'medium'
72
+ }
73
+ });
74
+ }
75
+ featureLayer.setGeoJSON(geojson);
76
+ map.fitBounds(featureLayer.getBounds());
77
+ </script>
78
+ <% elsif chart_type == "line" %>
79
+ <%= line_chart @columns[1..-1].each_with_index.map{ |k, i| {name: blazer_series_name(k), data: @rows.map{ |r| [r[0], r[i + 1]] }, library: series_library[i]} }, chart_options %>
80
+ <% elsif chart_type == "line2" %>
81
+ <%= line_chart @rows.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.map { |v2| [v2[0], v2[2]] }, library: series_library[i]} }, chart_options %>
82
+ <% elsif chart_type == "bar" %>
83
+ <%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: blazer_series_name(name), data: @rows.first(20).map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, id: chart_id %>
84
+ <% elsif chart_type == "bar2" %>
85
+ <% first_20 = @rows.group_by { |r| r[0] }.values.first(20).flatten(1) %>
86
+ <% labels = first_20.map { |r| r[0] }.uniq %>
87
+ <% series = first_20.map { |r| r[1] }.uniq %>
88
+ <% labels.each do |l| %>
89
+ <% series.each do |s| %>
90
+ <% first_20 << [l, s, 0] unless first_20.find { |r| r[0] == l && r[1] == s } %>
91
+ <% end %>
92
+ <% end %>
93
+ <%= column_chart first_20.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.sort_by { |r2| labels.index(r2[0]) }.map { |v2| v3 = v2[0]; [(@boom[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, id: chart_id %>
94
+ <% elsif chart_type == "scatter" %>
95
+ <%= scatter_chart @rows, xtitle: @columns[0], ytitle: @columns[1], id: chart_id %>
96
+ <% elsif @only_chart %>
97
+ <% if @rows.size == 1 && @rows.first.size == 1 %>
98
+ <% v = @rows.first.first %>
99
+ <% if v.is_a?(String) && v == "" %>
100
+ <div class="text-muted">empty string</div>
101
+ <% else %>
102
+ <p style="font-size: 160px;"><%= blazer_format_value(@columns.first, v) %></p>
103
+ <% end %>
104
+ <% else %>
105
+ <% @no_chart = true %>
106
+ <% end %>
107
+ <% end %>
108
+
109
+ <% unless @only_chart && !@no_chart %>
110
+ <% header_width = 100 / @columns.size.to_f %>
111
+ <div class="results-container">
112
+ <% if @columns == ["QUERY PLAN"] %>
113
+ <pre><code><%= @rows.map { |r| r[0] }.join("\n") %></code></pre>
114
+ <% else %>
115
+ <table class="table results-table" style="margin-bottom: 0;">
116
+ <thead>
117
+ <tr>
118
+ <% @columns.each_with_index do |key, i| %>
119
+ <% type = @column_types[i] %>
120
+ <th style="width: <%= header_width %>%;" data-sort="<%= type %>">
121
+ <div style="min-width: <%= @min_width_types.include?(i) ? 180 : 60 %>px;">
122
+ <%= key %>
123
+ </div>
124
+ </th>
125
+ <% end %>
126
+ </tr>
127
+ </thead>
128
+ <tbody>
129
+ <% @rows.each do |row| %>
130
+ <tr>
131
+ <% row.each_with_index do |v, i| %>
132
+ <% k = @columns[i] %>
133
+ <td>
134
+ <% if v.is_a?(Time) %>
135
+ <% v = blazer_time_value(@data_source, k, v) %>
136
+ <% end %>
137
+
138
+ <% unless v.nil? %>
139
+ <% if v.is_a?(String) && v == "" %>
140
+ <div class="text-muted">empty string</div>
141
+ <% elsif @linked_columns[k] %>
142
+ <%= link_to blazer_format_value(k, v), @linked_columns[k].gsub("{value}", u(v.to_s)), target: "_blank" %>
143
+ <% else %>
144
+ <%= blazer_format_value(k, v) %>
145
+ <% end %>
146
+ <% end %>
147
+
148
+ <% if v2 = (@boom[k] || {})[v.nil? ? v : v.to_s] %>
149
+ <div class="text-muted"><%= v2 %></div>
150
+ <% end %>
151
+ </td>
152
+ <% end %>
153
+ </tr>
154
+ <% end %>
155
+ </tbody>
156
+ </table>
157
+ <% end %>
158
+ </div>
159
+ <% end %>
160
+ <% elsif @only_chart %>
161
+ <p class="text-muted">No rows</p>
162
+ <% end %>
163
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <% @schema.each do |table| %>
2
+ <h4>
3
+ <%= table[:table] %>
4
+ <% if table[:schema] != "public" %>
5
+ <small><%= table[:schema] %></small>
6
+ <% end %>
7
+ </h4>
8
+ <table class="table" style="max-width: 500px;">
9
+ <tbody>
10
+ <% table[:columns].each do |column| %>
11
+ <tr>
12
+ <td style="width: 60%;"><%= column[:name] %></td>
13
+ <td class="text-muted"><%= column[:data_type] %></td>
14
+ </tr>
15
+ <% end %>
16
+ </tbody>
17
+ </table>
18
+ <% end %>
@@ -0,0 +1,73 @@
1
+ <% blazer_title @query.name %>
2
+
3
+ <div class="topbar">
4
+ <div class="container">
5
+ <div class="row" style="padding-top: 13px;">
6
+ <div class="col-sm-9">
7
+ <%= render partial: "blazer/nav" %>
8
+ <h3 style="margin: 0; line-height: 34px; display: inline;">
9
+ <%= @query.name %>
10
+ </h3>
11
+ </div>
12
+ <div class="col-sm-3 text-right">
13
+ <%= link_to "Edit", edit_query_path(@query, variable_params), class: "btn btn-default", disabled: !@query.editable?(blazer_user) %>
14
+ <%= link_to "Fork", new_query_path(variable_params.merge(fork_query_id: @query.id, data_source: @query.data_source, name: @query.name)), class: "btn btn-info" %>
15
+
16
+ <% if !@error && @success %>
17
+ <%= button_to "Download", run_queries_path(query_id: @query.id, format: "csv"), params: {statement: @statement}, class: "btn btn-primary" %>
18
+ <% end %>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </div>
23
+
24
+ <div style="margin-bottom: 60px;"></div>
25
+
26
+ <% if @sql_errors.any? %>
27
+ <div class="alert alert-danger">
28
+ <ul>
29
+ <% @sql_errors.each do |message| %>
30
+ <li><%= message %></li>
31
+ <% end %>
32
+ </ul>
33
+ </div>
34
+ <% end %>
35
+
36
+ <% if @query.description.present? %>
37
+ <p><%= @query.description %></p>
38
+ <% end %>
39
+
40
+ <%= render partial: "blazer/variables", locals: {action: query_path(@query)} %>
41
+
42
+ <pre id="code"><code><%= @statement %></code></pre>
43
+
44
+ <% if @success %>
45
+ <div id="results">
46
+ <p class="text-muted">Loading...</p>
47
+ </div>
48
+
49
+ <script>
50
+ function showRun(data) {
51
+ $("#results").html(data)
52
+ $("#results table").stupidtable().stickyTableHeaders({fixedOffset: 60})
53
+ }
54
+
55
+ function showError(message) {
56
+ $("#results").addClass("query-error").html(message)
57
+ }
58
+
59
+ <%= blazer_js_var "data", variable_params.merge(statement: @statement, query_id: @query.id) %>
60
+
61
+ runQuery(data, showRun, showError)
62
+ </script>
63
+ <% end %>
64
+
65
+ <% if %w[sql presto drill bigquery athena].include?(Blazer.data_sources[@query.data_source].adapter) %>
66
+ <script>
67
+ // do not highlight really long queries
68
+ // this can lead to performance issues
69
+ if ($("code").text().length < 10000) {
70
+ hljs.highlightBlock(document.getElementById("code"));
71
+ }
72
+ </script>
73
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= blazer_title ? blazer_title : "Blazer" %></title>
5
+
6
+ <meta charset="utf-8" />
7
+
8
+ <%= stylesheet_link_tag "blazer/application" %>
9
+ <%= javascript_include_tag "blazer/application" %>
10
+ <script>
11
+ <%= blazer_js_var "rootPath", root_path %>
12
+ </script>
13
+ <% if blazer_maps? %>
14
+ <%= stylesheet_link_tag "https://api.mapbox.com/mapbox.js/v2.4.0/mapbox.css" %>
15
+ <%= javascript_include_tag "https://api.mapbox.com/mapbox.js/v2.4.0/mapbox.js" %>
16
+ <% end %>
17
+ <%= csrf_meta_tags %>
18
+ </head>
19
+ <body>
20
+ <div class="container">
21
+ <%= yield %>
22
+ </div>
23
+ </body>
24
+ </html>
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "blazer/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sql-jarvis"
8
+ spec.version = Blazer::VERSION
9
+ spec.authors = ["ThanhKhoaIT"]
10
+ spec.email = ["thanhkhoait@gmail.com"]
11
+ spec.summary = "Fork from ankane! Explore your data with SQL. Easily create charts and dashboards, and share them with your team."
12
+ spec.homepage = "https://github.com/ThanhKhoaIT/blazer"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "rails", ">= 4"
21
+ spec.add_dependency "chartkick"
22
+ spec.add_dependency "safely_block", ">= 0.1.1"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.7"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ end
@@ -0,0 +1,16 @@
1
+ Blazer::Engine.routes.draw do
2
+ resources :queries do
3
+ post :run, on: :collection # err on the side of caution
4
+ post :cancel, on: :collection
5
+ post :refresh, on: :member
6
+ get :tables, on: :collection
7
+ get :schema, on: :collection
8
+ end
9
+ resources :checks, except: [:show] do
10
+ get :run, on: :member
11
+ end
12
+ resources :dashboards do
13
+ post :refresh, on: :member
14
+ end
15
+ root to: "queries#home"
16
+ end
@@ -0,0 +1,185 @@
1
+ require "csv"
2
+ require "yaml"
3
+ require "chartkick"
4
+ require "safely/core"
5
+ require "blazer/version"
6
+ require "blazer/data_source"
7
+ require "blazer/result"
8
+ require "blazer/run_statement"
9
+ require "blazer/adapters/base_adapter"
10
+ require "blazer/adapters/athena_adapter"
11
+ require "blazer/adapters/bigquery_adapter"
12
+ require "blazer/adapters/drill_adapter"
13
+ require "blazer/adapters/elasticsearch_adapter"
14
+ require "blazer/adapters/mongodb_adapter"
15
+ require "blazer/adapters/presto_adapter"
16
+ require "blazer/adapters/sql_adapter"
17
+ require "blazer/engine"
18
+
19
+ module Blazer
20
+ class Error < StandardError; end
21
+ class TimeoutNotSupported < Error; end
22
+
23
+ class << self
24
+ attr_accessor :audit
25
+ attr_reader :time_zone
26
+ attr_accessor :user_name
27
+ attr_accessor :user_class
28
+ attr_accessor :user_method
29
+ attr_accessor :before_action
30
+ attr_accessor :from_email
31
+ attr_accessor :cache
32
+ attr_accessor :transform_statement
33
+ attr_accessor :check_schedules
34
+ attr_accessor :mapbox_access_token
35
+ attr_accessor :anomaly_checks
36
+ attr_accessor :async
37
+ attr_accessor :images
38
+ attr_accessor :query_editable
39
+ end
40
+ self.audit = true
41
+ self.user_name = :name
42
+ self.check_schedules = ["5 minutes", "1 hour", "1 day"]
43
+ self.mapbox_access_token = nil
44
+ self.anomaly_checks = false
45
+ self.async = false
46
+ self.images = false
47
+
48
+ TIMEOUT_MESSAGE = "Query timed out :("
49
+ TIMEOUT_ERRORS = [
50
+ "canceling statement due to statement timeout", # postgres
51
+ "canceling statement due to conflict with recovery", # postgres
52
+ "cancelled on user's request", # redshift
53
+ "canceled on user's request", # redshift
54
+ "system requested abort", # redshift
55
+ "maximum statement execution time exceeded" # mysql
56
+ ]
57
+ BELONGS_TO_OPTIONAL = {}
58
+ BELONGS_TO_OPTIONAL[:optional] = true if Rails::VERSION::MAJOR >= 5
59
+
60
+ def self.time_zone=(time_zone)
61
+ @time_zone = time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone.to_s]
62
+ end
63
+
64
+ def self.settings
65
+ @settings ||= begin
66
+ path = Rails.root.join("config", "blazer.yml").to_s
67
+ if File.exist?(path)
68
+ YAML.load(ERB.new(File.read(path)).result)
69
+ else
70
+ {}
71
+ end
72
+ end
73
+ end
74
+
75
+ def self.data_sources
76
+ @data_sources ||= begin
77
+ ds = Hash[
78
+ settings["data_sources"].map do |id, s|
79
+ [id, Blazer::DataSource.new(id, s)]
80
+ end
81
+ ]
82
+ ds.default = ds.values.first
83
+ ds
84
+
85
+ # TODO Blazer 2.0
86
+ # ds2 = Hash.new { |hash, key| raise Blazer::Error, "Unknown data source: #{key}" }
87
+ # ds.each do |k, v|
88
+ # ds2[k] = v
89
+ # end
90
+ # ds2
91
+ end
92
+ end
93
+
94
+ def self.extract_vars(statement)
95
+ # strip commented out lines
96
+ # and regex {1} or {1,2}
97
+ statement.gsub(/\-\-.+/, "").gsub(/\/\*.+\*\//m, "").scan(/\{\w*?\}/i).map { |v| v[1...-1] }.reject { |v| /\A\d+(\,\d+)?\z/.match(v) || v.empty? }.uniq
98
+ end
99
+
100
+ def self.run_checks(schedule: nil)
101
+ checks = Blazer::Check.includes(:query)
102
+ checks = checks.where(schedule: schedule) if schedule
103
+ checks.find_each do |check|
104
+ next if check.state == "disabled"
105
+ Safely.safely { run_check(check) }
106
+ end
107
+ end
108
+
109
+ def self.run_check(check)
110
+ rows = nil
111
+ error = nil
112
+ tries = 1
113
+
114
+ ActiveSupport::Notifications.instrument("run_check.blazer", check_id: check.id, query_id: check.query.id, state_was: check.state) do |instrument|
115
+ # try 3 times on timeout errors
116
+ data_source = data_sources[check.query.data_source]
117
+ statement = check.query.statement
118
+ Blazer.transform_statement.call(data_source, statement) if Blazer.transform_statement
119
+
120
+ while tries <= 3
121
+ result = data_source.run_statement(statement, refresh_cache: true, check: check, query: check.query)
122
+ if result.timed_out?
123
+ Rails.logger.info "[blazer timeout] query=#{check.query.name}"
124
+ tries += 1
125
+ sleep(10)
126
+ elsif result.error.to_s.start_with?("PG::ConnectionBad")
127
+ data_source.reconnect
128
+ Rails.logger.info "[blazer reconnect] query=#{check.query.name}"
129
+ tries += 1
130
+ sleep(10)
131
+ else
132
+ break
133
+ end
134
+ end
135
+
136
+ begin
137
+ check.reload # in case state has changed since job started
138
+ check.update_state(result)
139
+ rescue ActiveRecord::RecordNotFound
140
+ # check deleted
141
+ end
142
+
143
+ # TODO use proper logfmt
144
+ Rails.logger.info "[blazer check] query=#{check.query.name} state=#{check.state} rows=#{result.rows.try(:size)} error=#{result.error}"
145
+
146
+ instrument[:statement] = statement
147
+ instrument[:data_source] = data_source
148
+ instrument[:state] = check.state
149
+ instrument[:rows] = result.rows.try(:size)
150
+ instrument[:error] = result.error
151
+ instrument[:tries] = tries
152
+ end
153
+ end
154
+
155
+ def self.send_failing_checks
156
+ emails = {}
157
+ Blazer::Check.includes(:query).where(state: ["failing", "error", "timed out", "disabled"]).find_each do |check|
158
+ check.split_emails.each do |email|
159
+ (emails[email] ||= []) << check
160
+ end
161
+ end
162
+
163
+ emails.each do |email, checks|
164
+ Safely.safely do
165
+ Blazer::CheckMailer.failing_checks(email, checks).deliver_now
166
+ end
167
+ end
168
+ end
169
+
170
+ def self.adapters
171
+ @adapters ||= {}
172
+ end
173
+
174
+ def self.register_adapter(name, adapter)
175
+ adapters[name] = adapter
176
+ end
177
+ end
178
+
179
+ Blazer.register_adapter "drill", Blazer::Adapters::DrillAdapter
180
+ Blazer.register_adapter "bigquery", Blazer::Adapters::BigQueryAdapter
181
+ Blazer.register_adapter "athena", Blazer::Adapters::AthenaAdapter
182
+ Blazer.register_adapter "elasticsearch", Blazer::Adapters::ElasticsearchAdapter
183
+ Blazer.register_adapter "mongodb", Blazer::Adapters::MongodbAdapter
184
+ Blazer.register_adapter "presto", Blazer::Adapters::PrestoAdapter
185
+ Blazer.register_adapter "sql", Blazer::Adapters::SqlAdapter