blazer 1.5.1 → 1.6.0
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +48 -3
- data/app/controllers/blazer/base_controller.rb +10 -2
- data/app/controllers/blazer/queries_controller.rb +1 -1
- data/app/views/blazer/check_mailer/failing_checks.html.erb +1 -1
- data/app/views/blazer/queries/_form.html.erb +3 -1
- data/app/views/blazer/queries/show.html.erb +10 -6
- data/lib/blazer.rb +5 -0
- data/lib/blazer/adapters/base_adapter.rb +41 -0
- data/lib/blazer/adapters/elasticsearch_adapter.rb +43 -0
- data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
- data/lib/blazer/adapters/sql_adapter.rb +120 -0
- data/lib/blazer/data_source.rb +24 -131
- data/lib/blazer/run_statement.rb +38 -0
- data/lib/blazer/run_statement_job.rb +8 -3
- data/lib/blazer/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04d14bae947237ea5450d0c8104572d41030e2ba
|
4
|
+
data.tar.gz: 1cf2e2ecad47ab458a653382728911dfaa82b1d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6331237e96ba16d02561cfeb1f2de6e95abdbc2b6f2069cdfa2a72309e92720137ae899498b4ac177abc9b3fb663293950006ff84e186560101b0786c9a6c75a
|
7
|
+
data.tar.gz: b71679dded9d87c6637c082da7a41bda1ff448122fbfd3de62b1a8d8b5604b14196a26a96d8b2a14b0db38e65b77a8f2e0299761b3c25f411955541ce276e865
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -12,7 +12,7 @@ Explore your data with SQL. Easily create charts and dashboards, and share them
|
|
12
12
|
|
13
13
|
## Features
|
14
14
|
|
15
|
-
- **Multiple data sources** -
|
15
|
+
- **Multiple data sources** - PostgreSQL, MySQL, Redshift, and [many more](#full-list)
|
16
16
|
- **Variables** - run the same queries with different values
|
17
17
|
- **Checks & alerts** - get emailed when bad data appears
|
18
18
|
- **Audits** - all queries are tracked
|
@@ -346,12 +346,57 @@ data_sources:
|
|
346
346
|
# ...
|
347
347
|
```
|
348
348
|
|
349
|
+
### Full List
|
350
|
+
|
351
|
+
- PostgreSQL
|
352
|
+
- MySQL
|
353
|
+
- SQL Server
|
354
|
+
- Oracle
|
355
|
+
- IBM DB2 and Informix
|
356
|
+
- SQLite
|
357
|
+
- [Redshift](#redshift)
|
358
|
+
- [MongoDB](#mongodb) [beta]
|
359
|
+
- [Elasticsearch](#elasticsearch) [beta]
|
360
|
+
|
361
|
+
You can also create an adapter for any other data store.
|
362
|
+
|
363
|
+
**Note:** In the examples below, we recommend using environment variables for urls.
|
364
|
+
|
365
|
+
```yml
|
366
|
+
data_sources:
|
367
|
+
my_source:
|
368
|
+
url: <%= ENV["BLAZER_MY_SOURCE_URL"] %>
|
369
|
+
```
|
370
|
+
|
349
371
|
### Redshift
|
350
372
|
|
351
373
|
Add [activerecord4-redshift-adapter](https://github.com/aamine/activerecord4-redshift-adapter) to your Gemfile and set:
|
352
374
|
|
353
|
-
```
|
354
|
-
|
375
|
+
```yml
|
376
|
+
data_sources:
|
377
|
+
my_source:
|
378
|
+
url: redshift://user:password@hostname:5439/database
|
379
|
+
```
|
380
|
+
|
381
|
+
### MongoDB
|
382
|
+
|
383
|
+
Add [mongo](https://github.com/mongodb/mongo-ruby-driver) to your Gemfile and set:
|
384
|
+
|
385
|
+
```yml
|
386
|
+
data_sources:
|
387
|
+
my_source:
|
388
|
+
url: mongodb://user:password@hostname:27017/database
|
389
|
+
```
|
390
|
+
|
391
|
+
### Elasticsearch
|
392
|
+
|
393
|
+
Add [elasticsearch](https://github.com/elastic/elasticsearch-ruby) to your Gemfile and set:
|
394
|
+
|
395
|
+
```yml
|
396
|
+
data_sources:
|
397
|
+
my_source:
|
398
|
+
adapter: elasticsearch
|
399
|
+
url: http://user:password@hostname:9200/
|
355
400
|
```
|
356
401
|
|
357
402
|
## Learn SQL
|
@@ -1,7 +1,15 @@
|
|
1
1
|
module Blazer
|
2
2
|
class BaseController < ApplicationController
|
3
3
|
# skip all filters
|
4
|
-
|
4
|
+
filters = _process_action_callbacks.map(&:filter)
|
5
|
+
if Rails::VERSION::MAJOR >= 5
|
6
|
+
skip_before_action(*filters, raise: false)
|
7
|
+
skip_after_action(*filters, raise: false)
|
8
|
+
skip_around_action(*filters, raise: false)
|
9
|
+
before_action :verify_request_size
|
10
|
+
else
|
11
|
+
skip_action_callback *filters
|
12
|
+
end
|
5
13
|
|
6
14
|
protect_from_forgery with: :exception
|
7
15
|
|
@@ -46,7 +54,7 @@ module Blazer
|
|
46
54
|
def extract_vars(statement)
|
47
55
|
# strip commented out lines
|
48
56
|
# and regex {1} or {1,2}
|
49
|
-
statement.gsub(/\-\-.+/, "").gsub(/\/\*.+\*\//m, "").scan(/\{
|
57
|
+
statement.gsub(/\-\-.+/, "").gsub(/\/\*.+\*\//m, "").scan(/\{\w*?\}/i).map { |v| v[1...-1] }.reject { |v| /\A\d+(\,\d+)?\z/.match(v) || v.empty? }.uniq
|
50
58
|
end
|
51
59
|
helper_method :extract_vars
|
52
60
|
|
@@ -126,6 +126,7 @@
|
|
126
126
|
var error_line = null;
|
127
127
|
var xhr;
|
128
128
|
var params = <%= raw blazer_json_escape(variable_params.to_json) %>;
|
129
|
+
var previewStatement = <%= raw blazer_json_escape(Hash[Blazer.data_sources.map { |k, v| [k, v.preview_statement] }].to_json) %>;
|
129
130
|
|
130
131
|
$("#run").click(function (e) {
|
131
132
|
e.preventDefault();
|
@@ -162,7 +163,8 @@
|
|
162
163
|
$(document).on("change", "#table_names", function () {
|
163
164
|
var val = $(this).val();
|
164
165
|
if (val.length > 0) {
|
165
|
-
|
166
|
+
var dataSource = $("#query_data_source").val();
|
167
|
+
editor.setValue(previewStatement[dataSource].replace("{table}", val));
|
166
168
|
$("#run").click();
|
167
169
|
}
|
168
170
|
});
|
@@ -172,13 +172,17 @@
|
|
172
172
|
</script>
|
173
173
|
<% end %>
|
174
174
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
175
|
+
<% if Blazer.data_sources[@query.data_source].adapter == "activerecord" %>
|
176
|
+
<script>
|
177
|
+
// do not highlight really long queries
|
178
|
+
// this can lead to performance issues
|
179
|
+
if ($("code").text().length < 10000) {
|
180
|
+
hljs.initHighlightingOnLoad();
|
181
|
+
}
|
182
|
+
</script>
|
183
|
+
<% end %>
|
181
184
|
|
185
|
+
<script>
|
182
186
|
$(".form-inline input, .form-inline select").change( function () {
|
183
187
|
submitIfCompleted($(this).closest("form"));
|
184
188
|
});
|
data/lib/blazer.rb
CHANGED
@@ -5,6 +5,11 @@ require "safely/core"
|
|
5
5
|
require "blazer/version"
|
6
6
|
require "blazer/data_source"
|
7
7
|
require "blazer/result"
|
8
|
+
require "blazer/run_statement"
|
9
|
+
require "blazer/adapters/base_adapter"
|
10
|
+
require "blazer/adapters/elasticsearch_adapter"
|
11
|
+
require "blazer/adapters/mongodb_adapter"
|
12
|
+
require "blazer/adapters/sql_adapter"
|
8
13
|
require "blazer/engine"
|
9
14
|
|
10
15
|
module Blazer
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class BaseAdapter
|
4
|
+
attr_reader :data_source
|
5
|
+
|
6
|
+
def initialize(data_source)
|
7
|
+
@data_source = data_source
|
8
|
+
end
|
9
|
+
|
10
|
+
def run_statement(statement, comment)
|
11
|
+
# the one required method
|
12
|
+
end
|
13
|
+
|
14
|
+
def tables
|
15
|
+
[] # optional, but nice to have
|
16
|
+
end
|
17
|
+
|
18
|
+
def preview_statement
|
19
|
+
"" # also optional, but nice to have
|
20
|
+
end
|
21
|
+
|
22
|
+
def reconnect
|
23
|
+
# optional
|
24
|
+
end
|
25
|
+
|
26
|
+
def cost(statement)
|
27
|
+
# optional
|
28
|
+
end
|
29
|
+
|
30
|
+
def explain(statement)
|
31
|
+
# optional
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def settings
|
37
|
+
@data_source.settings
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class ElasticsearchAdapter < BaseAdapter
|
4
|
+
def run_statement(statement, comment)
|
5
|
+
columns = []
|
6
|
+
rows = []
|
7
|
+
error = nil
|
8
|
+
|
9
|
+
begin
|
10
|
+
header, body = statement.gsub(/\/\/.+/, "").strip.split("\n", 2)
|
11
|
+
response = client.msearch(body: [JSON.parse(header), JSON.parse(body)])["responses"].first
|
12
|
+
hits = response["hits"]["hits"]
|
13
|
+
source_keys = hits.flat_map { |r| r["_source"].keys }.uniq
|
14
|
+
hit_keys = (hits.first.try(:keys) || []) - ["_source"]
|
15
|
+
columns = source_keys + hit_keys
|
16
|
+
rows =
|
17
|
+
hits.map do |r|
|
18
|
+
source = r["_source"]
|
19
|
+
source_keys.map { |k| source[k] } + hit_keys.map { |k| r[k] }
|
20
|
+
end
|
21
|
+
rescue => e
|
22
|
+
error = e.message
|
23
|
+
end
|
24
|
+
|
25
|
+
[columns, rows, error]
|
26
|
+
end
|
27
|
+
|
28
|
+
def tables
|
29
|
+
client.indices.get_aliases.map { |k, v| [k, v["aliases"].keys] }.flatten.uniq.sort
|
30
|
+
end
|
31
|
+
|
32
|
+
def preview_statement
|
33
|
+
%!// header\n{"index": "{table}"}\n\n// body\n{"query": {"match_all": {}}, "size": 10}!
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def client
|
39
|
+
@client ||= Elasticsearch::Client.new(url: settings["url"])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class MongodbAdapter < BaseAdapter
|
4
|
+
def run_statement(statement, comment)
|
5
|
+
columns = []
|
6
|
+
rows = []
|
7
|
+
error = nil
|
8
|
+
|
9
|
+
begin
|
10
|
+
documents = db.command({:$eval => "#{statement.strip}.toArray()"}).documents.first["retval"]
|
11
|
+
columns = documents.flat_map { |r| r.keys }.uniq
|
12
|
+
rows = documents.map { |r| columns.map { |c| r[c] } }
|
13
|
+
rescue => e
|
14
|
+
error = e.message
|
15
|
+
end
|
16
|
+
|
17
|
+
[columns, rows, error]
|
18
|
+
end
|
19
|
+
|
20
|
+
def tables
|
21
|
+
db.collection_names
|
22
|
+
end
|
23
|
+
|
24
|
+
def preview_statement
|
25
|
+
"db.{table}.find().limit(10)"
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def client
|
31
|
+
@client ||= Mongo::Client.new(settings["url"])
|
32
|
+
end
|
33
|
+
|
34
|
+
def db
|
35
|
+
@db ||= client.database
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,120 @@
|
|
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::#{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 = connection_model.connection.select_all("#{statement} /*#{comment}*/")
|
28
|
+
columns = result.columns
|
29
|
+
cast_method = Rails::VERSION::MAJOR < 5 ? :type_cast : :cast_value
|
30
|
+
result.rows.each do |untyped_row|
|
31
|
+
rows << (result.column_types.empty? ? untyped_row : columns.each_with_index.map { |c, i| untyped_row[i] ? result.column_types[c].send(cast_method, untyped_row[i]) : nil })
|
32
|
+
end
|
33
|
+
end
|
34
|
+
rescue ActiveRecord::StatementInvalid => e
|
35
|
+
error = e.message.sub(/.+ERROR: /, "")
|
36
|
+
error = Blazer::TIMEOUT_MESSAGE if Blazer::TIMEOUT_ERRORS.any? { |e| error.include?(e) }
|
37
|
+
end
|
38
|
+
|
39
|
+
[columns, rows, error]
|
40
|
+
end
|
41
|
+
|
42
|
+
def tables
|
43
|
+
result = data_source.run_statement(connection_model.send(:sanitize_sql_array, ["SELECT table_name FROM information_schema.tables WHERE table_schema IN (?) ORDER BY table_name", schemas]))
|
44
|
+
result.rows.map(&:first)
|
45
|
+
end
|
46
|
+
|
47
|
+
def preview_statement
|
48
|
+
"SELECT * FROM {table} LIMIT 10"
|
49
|
+
end
|
50
|
+
|
51
|
+
def reconnect
|
52
|
+
connection_model.establish_connection(settings["url"])
|
53
|
+
end
|
54
|
+
|
55
|
+
def cost(statement)
|
56
|
+
result = explain(statement)
|
57
|
+
match = /cost=\d+\.\d+..(\d+\.\d+) /.match(result)
|
58
|
+
match[1] if match
|
59
|
+
end
|
60
|
+
|
61
|
+
def explain(statement)
|
62
|
+
if postgresql? || redshift?
|
63
|
+
connection_model.connection.select_all("EXPLAIN #{statement}").rows.first.first
|
64
|
+
end
|
65
|
+
rescue
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def postgresql?
|
72
|
+
["PostgreSQL", "PostGIS"].include?(adapter_name)
|
73
|
+
end
|
74
|
+
|
75
|
+
def redshift?
|
76
|
+
["Redshift"].include?(adapter_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def mysql?
|
80
|
+
["MySQL", "Mysql2", "Mysql2Spatial"].include?(adapter_name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def adapter_name
|
84
|
+
connection_model.connection.adapter_name
|
85
|
+
end
|
86
|
+
|
87
|
+
def schemas
|
88
|
+
default_schema = (postgresql? || redshift?) ? "public" : connection_model.connection_config[:database]
|
89
|
+
settings["schemas"] || [connection_model.connection_config[:schema] || default_schema]
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_timeout(timeout)
|
93
|
+
if postgresql? || redshift?
|
94
|
+
connection_model.connection.execute("SET statement_timeout = #{timeout.to_i * 1000}")
|
95
|
+
elsif mysql?
|
96
|
+
connection_model.connection.execute("SET max_execution_time = #{timeout.to_i * 1000}")
|
97
|
+
else
|
98
|
+
raise Blazer::TimeoutNotSupported, "Timeout not supported for #{adapter_name} adapter"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def use_transaction?
|
103
|
+
settings.key?("use_transaction") ? settings["use_transaction"] : true
|
104
|
+
end
|
105
|
+
|
106
|
+
def in_transaction
|
107
|
+
connection_model.connection_pool.with_connection do
|
108
|
+
if use_transaction?
|
109
|
+
connection_model.transaction do
|
110
|
+
yield
|
111
|
+
raise ActiveRecord::Rollback
|
112
|
+
end
|
113
|
+
else
|
114
|
+
yield
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/blazer/data_source.rb
CHANGED
@@ -2,7 +2,11 @@ require "digest/md5"
|
|
2
2
|
|
3
3
|
module Blazer
|
4
4
|
class DataSource
|
5
|
-
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :id, :settings, :adapter, :adapter_instance
|
8
|
+
|
9
|
+
def_delegators :adapter_instance, :schema, :tables, :preview_statement, :reconnect, :cost, :explain
|
6
10
|
|
7
11
|
def initialize(id, settings)
|
8
12
|
@id = id
|
@@ -12,15 +16,23 @@ module Blazer
|
|
12
16
|
raise Blazer::Error, "Empty url"
|
13
17
|
end
|
14
18
|
|
15
|
-
@
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
@adapter_instance =
|
20
|
+
case adapter
|
21
|
+
when "sql"
|
22
|
+
Blazer::Adapters::SqlAdapter.new(self)
|
23
|
+
when "elasticsearch"
|
24
|
+
Blazer::Adapters::ElasticsearchAdapter.new(self)
|
25
|
+
when "mongodb"
|
26
|
+
Blazer::Adapters::MongodbAdapter.new(self)
|
27
|
+
else
|
28
|
+
raise Blazer::Error, "Unknown adapter"
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
32
|
+
def adapter
|
33
|
+
settings["adapter"] || detect_adapter
|
34
|
+
end
|
35
|
+
|
24
36
|
def name
|
25
37
|
settings["name"] || @id
|
26
38
|
end
|
@@ -78,61 +90,6 @@ module Blazer
|
|
78
90
|
@local_time_suffix ||= Array(settings["local_time_suffix"])
|
79
91
|
end
|
80
92
|
|
81
|
-
def use_transaction?
|
82
|
-
settings.key?("use_transaction") ? settings["use_transaction"] : true
|
83
|
-
end
|
84
|
-
|
85
|
-
def cost(statement)
|
86
|
-
result = explain(statement)
|
87
|
-
match = /cost=\d+\.\d+..(\d+\.\d+) /.match(result)
|
88
|
-
match[1] if match
|
89
|
-
end
|
90
|
-
|
91
|
-
def explain(statement)
|
92
|
-
if postgresql? || redshift?
|
93
|
-
connection_model.connection.select_all("EXPLAIN #{statement}").rows.first.first
|
94
|
-
end
|
95
|
-
rescue
|
96
|
-
nil
|
97
|
-
end
|
98
|
-
|
99
|
-
def run_main_statement(statement, options = {})
|
100
|
-
query = options[:query]
|
101
|
-
Blazer.transform_statement.call(self, statement) if Blazer.transform_statement
|
102
|
-
|
103
|
-
# audit
|
104
|
-
if Blazer.audit
|
105
|
-
audit = Blazer::Audit.new(statement: statement)
|
106
|
-
audit.query = query
|
107
|
-
audit.data_source = id
|
108
|
-
audit.user = options[:user]
|
109
|
-
audit.save!
|
110
|
-
end
|
111
|
-
|
112
|
-
start_time = Time.now
|
113
|
-
result = run_statement(statement, options)
|
114
|
-
duration = Time.now - start_time
|
115
|
-
|
116
|
-
if Blazer.audit
|
117
|
-
audit.duration = duration if audit.respond_to?(:duration=)
|
118
|
-
audit.error = result.error if audit.respond_to?(:error=)
|
119
|
-
audit.timed_out = result.timed_out? if audit.respond_to?(:timed_out=)
|
120
|
-
audit.cached = result.cached? if audit.respond_to?(:cached=)
|
121
|
-
if !result.cached? && duration >= 10
|
122
|
-
audit.cost = cost(statement) if audit.respond_to?(:cost=)
|
123
|
-
end
|
124
|
-
audit.save! if audit.changed?
|
125
|
-
end
|
126
|
-
|
127
|
-
if query && !result.timed_out?
|
128
|
-
query.checks.each do |check|
|
129
|
-
check.update_state(result)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
result
|
134
|
-
end
|
135
|
-
|
136
93
|
def read_cache(cache_key)
|
137
94
|
value = Blazer.cache.read(cache_key)
|
138
95
|
if value
|
@@ -192,70 +149,13 @@ module Blazer
|
|
192
149
|
cache_key(["run", run_id])
|
193
150
|
end
|
194
151
|
|
195
|
-
def schemas
|
196
|
-
default_schema = (postgresql? || redshift?) ? "public" : connection_model.connection_config[:database]
|
197
|
-
settings["schemas"] || [connection_model.connection_config[:schema] || default_schema]
|
198
|
-
end
|
199
|
-
|
200
|
-
def tables
|
201
|
-
result = run_statement(connection_model.send(:sanitize_sql_array, ["SELECT table_name FROM information_schema.tables WHERE table_schema IN (?) ORDER BY table_name", schemas]))
|
202
|
-
result.rows.map(&:first)
|
203
|
-
end
|
204
|
-
|
205
|
-
def postgresql?
|
206
|
-
["PostgreSQL", "PostGIS"].include?(adapter_name)
|
207
|
-
end
|
208
|
-
|
209
|
-
def redshift?
|
210
|
-
["Redshift"].include?(adapter_name)
|
211
|
-
end
|
212
|
-
|
213
|
-
def mysql?
|
214
|
-
["MySQL", "Mysql2", "Mysql2Spatial"].include?(adapter_name)
|
215
|
-
end
|
216
|
-
|
217
|
-
def reconnect
|
218
|
-
connection_model.establish_connection(settings["url"])
|
219
|
-
end
|
220
|
-
|
221
152
|
protected
|
222
153
|
|
223
154
|
def run_statement_helper(statement, comment, run_id)
|
224
|
-
columns = []
|
225
|
-
rows = []
|
226
|
-
error = nil
|
227
155
|
start_time = Time.now
|
228
|
-
|
229
|
-
|
230
|
-
begin
|
231
|
-
in_transaction do
|
232
|
-
if timeout
|
233
|
-
if postgresql? || redshift?
|
234
|
-
connection_model.connection.execute("SET statement_timeout = #{timeout.to_i * 1000}")
|
235
|
-
elsif mysql?
|
236
|
-
connection_model.connection.execute("SET max_execution_time = #{timeout.to_i * 1000}")
|
237
|
-
else
|
238
|
-
raise Blazer::TimeoutNotSupported, "Timeout not supported for #{adapter_name} adapter"
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
result = connection_model.connection.select_all("#{statement} /*#{comment}*/")
|
243
|
-
end
|
244
|
-
rescue ActiveRecord::StatementInvalid => e
|
245
|
-
error = e.message.sub(/.+ERROR: /, "")
|
246
|
-
error = Blazer::TIMEOUT_MESSAGE if Blazer::TIMEOUT_ERRORS.any? { |e| error.include?(e) }
|
247
|
-
end
|
248
|
-
|
156
|
+
columns, rows, error = @adapter_instance.run_statement(statement, comment)
|
249
157
|
duration = Time.now - start_time
|
250
158
|
|
251
|
-
if result
|
252
|
-
columns = result.columns
|
253
|
-
cast_method = Rails::VERSION::MAJOR < 5 ? :type_cast : :cast_value
|
254
|
-
result.rows.each do |untyped_row|
|
255
|
-
rows << (result.column_types.empty? ? untyped_row : columns.each_with_index.map { |c, i| untyped_row[i] ? result.column_types[c].send(cast_method, untyped_row[i]) : nil })
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
159
|
cache_data = nil
|
260
160
|
cache = !error && (cache_mode == "all" || (cache_mode == "slow" && duration >= cache_slow_threshold))
|
261
161
|
if cache || run_id
|
@@ -277,18 +177,11 @@ module Blazer
|
|
277
177
|
Blazer::Result.new(self, columns, rows, error, nil, cache && !cache_data.nil?)
|
278
178
|
end
|
279
179
|
|
280
|
-
def
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
def in_transaction
|
285
|
-
if use_transaction?
|
286
|
-
connection_model.transaction do
|
287
|
-
yield
|
288
|
-
raise ActiveRecord::Rollback
|
289
|
-
end
|
180
|
+
def detect_adapter
|
181
|
+
if settings["url"].to_s.start_with?("mongodb://")
|
182
|
+
"mongodb"
|
290
183
|
else
|
291
|
-
|
184
|
+
"sql"
|
292
185
|
end
|
293
186
|
end
|
294
187
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class RunStatement
|
2
|
+
def perform(data_source, statement, options = {})
|
3
|
+
query = options[:query]
|
4
|
+
Blazer.transform_statement.call(data_source, statement) if Blazer.transform_statement
|
5
|
+
|
6
|
+
# audit
|
7
|
+
if Blazer.audit
|
8
|
+
audit = Blazer::Audit.new(statement: statement)
|
9
|
+
audit.query = query
|
10
|
+
audit.data_source = data_source.id
|
11
|
+
audit.user = options[:user]
|
12
|
+
audit.save!
|
13
|
+
end
|
14
|
+
|
15
|
+
start_time = Time.now
|
16
|
+
result = data_source.run_statement(statement, options)
|
17
|
+
duration = Time.now - start_time
|
18
|
+
|
19
|
+
if Blazer.audit
|
20
|
+
audit.duration = duration if audit.respond_to?(:duration=)
|
21
|
+
audit.error = result.error if audit.respond_to?(:error=)
|
22
|
+
audit.timed_out = result.timed_out? if audit.respond_to?(:timed_out=)
|
23
|
+
audit.cached = result.cached? if audit.respond_to?(:cached=)
|
24
|
+
if !result.cached? && duration >= 10
|
25
|
+
audit.cost = data_source.cost(statement) if audit.respond_to?(:cost=)
|
26
|
+
end
|
27
|
+
audit.save! if audit.changed?
|
28
|
+
end
|
29
|
+
|
30
|
+
if query && !result.timed_out?
|
31
|
+
query.checks.each do |check|
|
32
|
+
check.update_state(result)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
result
|
37
|
+
end
|
38
|
+
end
|
@@ -6,10 +6,15 @@ module Blazer
|
|
6
6
|
workers 4
|
7
7
|
|
8
8
|
def perform(result, data_source, statement, options)
|
9
|
-
|
10
|
-
|
11
|
-
result <<
|
9
|
+
begin
|
10
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
11
|
+
result << RunStatement.new.perform(data_source, statement, options)
|
12
12
|
end
|
13
|
+
rescue Exception => e
|
14
|
+
result.clear
|
15
|
+
result << Blazer::Result.new(data_source, [], [], "Unknown error", nil, false)
|
16
|
+
Blazer.cache.write(data_source.run_cache_key(options[:run_id]), Marshal.dump([[], [], "Unknown error", nil]), expires_in: 30.seconds)
|
17
|
+
raise e
|
13
18
|
end
|
14
19
|
end
|
15
20
|
end
|
data/lib/blazer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blazer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07-
|
11
|
+
date: 2016-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -159,10 +159,15 @@ files:
|
|
159
159
|
- blazer.gemspec
|
160
160
|
- config/routes.rb
|
161
161
|
- lib/blazer.rb
|
162
|
+
- lib/blazer/adapters/base_adapter.rb
|
163
|
+
- lib/blazer/adapters/elasticsearch_adapter.rb
|
164
|
+
- lib/blazer/adapters/mongodb_adapter.rb
|
165
|
+
- lib/blazer/adapters/sql_adapter.rb
|
162
166
|
- lib/blazer/data_source.rb
|
163
167
|
- lib/blazer/detect_anomalies.R
|
164
168
|
- lib/blazer/engine.rb
|
165
169
|
- lib/blazer/result.rb
|
170
|
+
- lib/blazer/run_statement.rb
|
166
171
|
- lib/blazer/run_statement_job.rb
|
167
172
|
- lib/blazer/version.rb
|
168
173
|
- lib/generators/blazer/install_generator.rb
|