blazer 2.5.0 → 2.6.4
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/CHANGELOG.md +25 -0
- data/README.md +63 -15
- data/app/assets/javascripts/blazer/queries.js +12 -1
- data/app/assets/stylesheets/blazer/application.css +1 -0
- data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
- data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
- data/app/assets/stylesheets/blazer/{bootstrap.css.erb → bootstrap.css} +0 -6
- data/app/controllers/blazer/base_controller.rb +45 -45
- data/app/controllers/blazer/dashboards_controller.rb +4 -11
- data/app/controllers/blazer/queries_controller.rb +29 -48
- data/app/models/blazer/query.rb +8 -2
- data/app/views/blazer/_variables.html.erb +5 -4
- data/app/views/blazer/dashboards/_form.html.erb +1 -1
- data/app/views/blazer/dashboards/show.html.erb +6 -4
- data/app/views/blazer/queries/_caching.html.erb +1 -1
- data/app/views/blazer/queries/_form.html.erb +3 -3
- data/app/views/blazer/queries/run.html.erb +1 -1
- data/app/views/blazer/queries/show.html.erb +12 -7
- data/app/views/layouts/blazer/application.html.erb +7 -2
- data/lib/blazer/adapters/athena_adapter.rb +55 -18
- data/lib/blazer/adapters/base_adapter.rb +16 -1
- data/lib/blazer/adapters/bigquery_adapter.rb +13 -2
- data/lib/blazer/adapters/cassandra_adapter.rb +15 -4
- data/lib/blazer/adapters/drill_adapter.rb +10 -0
- data/lib/blazer/adapters/druid_adapter.rb +36 -1
- data/lib/blazer/adapters/elasticsearch_adapter.rb +13 -2
- data/lib/blazer/adapters/hive_adapter.rb +10 -0
- data/lib/blazer/adapters/ignite_adapter.rb +12 -2
- data/lib/blazer/adapters/influxdb_adapter.rb +22 -10
- data/lib/blazer/adapters/mongodb_adapter.rb +4 -0
- data/lib/blazer/adapters/neo4j_adapter.rb +17 -2
- data/lib/blazer/adapters/opensearch_adapter.rb +4 -0
- data/lib/blazer/adapters/presto_adapter.rb +9 -0
- data/lib/blazer/adapters/salesforce_adapter.rb +5 -0
- data/lib/blazer/adapters/snowflake_adapter.rb +9 -0
- data/lib/blazer/adapters/soda_adapter.rb +9 -0
- data/lib/blazer/adapters/spark_adapter.rb +5 -0
- data/lib/blazer/adapters/sql_adapter.rb +37 -3
- data/lib/blazer/data_source.rb +85 -5
- data/lib/blazer/engine.rb +0 -4
- data/lib/blazer/result.rb +2 -0
- data/lib/blazer/run_statement.rb +7 -3
- data/lib/blazer/run_statement_job.rb +4 -2
- data/lib/blazer/slack_notifier.rb +5 -2
- data/lib/blazer/statement.rb +75 -0
- data/lib/blazer/version.rb +1 -1
- data/lib/blazer.rb +14 -6
- metadata +7 -4
@@ -52,29 +52,26 @@ module Blazer
|
|
52
52
|
@query.status = "active" if @query.respond_to?(:status)
|
53
53
|
|
54
54
|
if @query.save
|
55
|
-
redirect_to query_path(@query, variable_params(@query))
|
55
|
+
redirect_to query_path(@query, params: variable_params(@query))
|
56
56
|
else
|
57
57
|
render_errors @query
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
61
|
def show
|
62
|
-
@statement = @query.
|
63
|
-
process_vars(@statement
|
62
|
+
@statement = @query.statement_object
|
63
|
+
@success = process_vars(@statement)
|
64
64
|
|
65
65
|
@smart_vars = {}
|
66
66
|
@sql_errors = []
|
67
|
-
data_source = Blazer.data_sources[@query.data_source]
|
68
67
|
@bind_vars.each do |var|
|
69
|
-
smart_var, error = parse_smart_variables(var, data_source)
|
68
|
+
smart_var, error = parse_smart_variables(var, @statement.data_source)
|
70
69
|
@smart_vars[var] = smart_var if smart_var
|
71
70
|
@sql_errors << error if error
|
72
71
|
end
|
73
72
|
|
74
73
|
@query.update!(status: "active") if @query.respond_to?(:status) && @query.status.in?(["archived", nil])
|
75
74
|
|
76
|
-
Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
|
77
|
-
|
78
75
|
add_cohort_analysis_vars if @query.cohort_analysis?
|
79
76
|
end
|
80
77
|
|
@@ -82,17 +79,25 @@ module Blazer
|
|
82
79
|
end
|
83
80
|
|
84
81
|
def run
|
85
|
-
@statement = params[:statement]
|
86
|
-
# before process_vars
|
87
|
-
@cohort_analysis = Query.new(statement: @statement).cohort_analysis?
|
88
|
-
data_source = params[:data_source]
|
89
|
-
process_vars(@statement, data_source)
|
90
|
-
@only_chart = params[:only_chart]
|
91
|
-
@run_id = blazer_params[:run_id]
|
92
82
|
@query = Query.find_by(id: params[:query_id]) if params[:query_id]
|
83
|
+
|
84
|
+
# use query data source when present
|
85
|
+
# need to update viewable? logic below if this changes
|
93
86
|
data_source = @query.data_source if @query && @query.data_source
|
87
|
+
data_source ||= params[:data_source]
|
94
88
|
@data_source = Blazer.data_sources[data_source]
|
95
89
|
|
90
|
+
@statement = Blazer::Statement.new(params[:statement], @data_source)
|
91
|
+
# before process_vars
|
92
|
+
@cohort_analysis = @statement.cohort_analysis?
|
93
|
+
|
94
|
+
# fallback for now for users with open tabs
|
95
|
+
# TODO remove fallback in future version
|
96
|
+
@var_params = request.request_parameters["variables"] || request.request_parameters
|
97
|
+
@success = process_vars(@statement, @var_params)
|
98
|
+
@only_chart = params[:only_chart]
|
99
|
+
@run_id = blazer_params[:run_id]
|
100
|
+
|
96
101
|
run_cohort_analysis if @cohort_analysis
|
97
102
|
|
98
103
|
# ensure viewable
|
@@ -128,7 +133,7 @@ module Blazer
|
|
128
133
|
|
129
134
|
options = {user: blazer_user, query: @query, refresh_cache: params[:check], run_id: @run_id, async: Blazer.async}
|
130
135
|
if Blazer.async && request.format.symbol != :csv
|
131
|
-
Blazer::RunStatementJob.perform_later(@data_source.id, @statement, options)
|
136
|
+
Blazer::RunStatementJob.perform_later(@data_source.id, @statement.statement, options.merge(values: @statement.values))
|
132
137
|
wait_start = Time.now
|
133
138
|
loop do
|
134
139
|
sleep(0.1)
|
@@ -136,7 +141,7 @@ module Blazer
|
|
136
141
|
break if @result || Time.now - wait_start > 3
|
137
142
|
end
|
138
143
|
else
|
139
|
-
@result = Blazer::RunStatement.new.perform(@
|
144
|
+
@result = Blazer::RunStatement.new.perform(@statement, options)
|
140
145
|
end
|
141
146
|
|
142
147
|
if @result
|
@@ -166,13 +171,8 @@ module Blazer
|
|
166
171
|
end
|
167
172
|
|
168
173
|
def refresh
|
169
|
-
|
170
|
-
@
|
171
|
-
process_vars(@statement, @query.data_source)
|
172
|
-
Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
|
173
|
-
@statement = cohort_analysis_statement(data_source, @statement) if @query.cohort_analysis?
|
174
|
-
data_source.clear_cache(@statement)
|
175
|
-
redirect_to query_path(@query, variable_params(@query))
|
174
|
+
refresh_query(@query)
|
175
|
+
redirect_to query_path(@query, params: variable_params(@query))
|
176
176
|
end
|
177
177
|
|
178
178
|
def update
|
@@ -185,7 +185,7 @@ module Blazer
|
|
185
185
|
@query.errors.add(:base, "Sorry, permission denied")
|
186
186
|
end
|
187
187
|
if @query.errors.empty? && @query.update(query_params)
|
188
|
-
redirect_to query_path(@query, variable_params(@query))
|
188
|
+
redirect_to query_path(@query, params: variable_params(@query))
|
189
189
|
else
|
190
190
|
render_errors @query
|
191
191
|
end
|
@@ -282,6 +282,9 @@ module Blazer
|
|
282
282
|
render layout: false
|
283
283
|
end
|
284
284
|
format.csv do
|
285
|
+
# not ideal, but useful for testing
|
286
|
+
raise Error, @error if @error && Rails.env.test?
|
287
|
+
|
285
288
|
send_data csv_data(@columns, @rows, @data_source), type: "text/csv; charset=utf-8; header=present", disposition: "attachment; filename=\"#{@query.try(:name).try(:parameterize).presence || 'query'}.csv\""
|
286
289
|
end
|
287
290
|
end
|
@@ -363,34 +366,12 @@ module Blazer
|
|
363
366
|
end
|
364
367
|
|
365
368
|
def run_cohort_analysis
|
366
|
-
unless @data_source.supports_cohort_analysis?
|
369
|
+
unless @statement.data_source.supports_cohort_analysis?
|
367
370
|
@cohort_error = "This data source does not support cohort analysis"
|
368
371
|
end
|
369
372
|
|
370
373
|
@show_cohort_rows = !params[:query_id] || @cohort_error
|
371
|
-
|
372
|
-
unless @show_cohort_rows
|
373
|
-
@statement = cohort_analysis_statement(@data_source, @statement)
|
374
|
-
end
|
375
|
-
end
|
376
|
-
|
377
|
-
def cohort_analysis_statement(data_source, statement)
|
378
|
-
@cohort_period = params["cohort_period"] || "week"
|
379
|
-
@cohort_period = "week" unless ["day", "week", "month"].include?(@cohort_period)
|
380
|
-
|
381
|
-
# for now
|
382
|
-
@conversion_period = @cohort_period
|
383
|
-
@cohort_days =
|
384
|
-
case @cohort_period
|
385
|
-
when "day"
|
386
|
-
1
|
387
|
-
when "week"
|
388
|
-
7
|
389
|
-
when "month"
|
390
|
-
30
|
391
|
-
end
|
392
|
-
|
393
|
-
data_source.cohort_analysis_statement(statement, period: @cohort_period, days: @cohort_days)
|
374
|
+
cohort_analysis_statement(@statement) unless @show_cohort_rows
|
394
375
|
end
|
395
376
|
|
396
377
|
def render_cohort_analysis
|
data/app/models/blazer/query.rb
CHANGED
@@ -35,13 +35,19 @@ module Blazer
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def variables
|
38
|
-
|
38
|
+
# don't require data_source to be loaded
|
39
|
+
variables = Statement.new(statement).variables
|
39
40
|
variables += ["cohort_period"] if cohort_analysis?
|
40
41
|
variables
|
41
42
|
end
|
42
43
|
|
43
44
|
def cohort_analysis?
|
44
|
-
|
45
|
+
# don't require data_source to be loaded
|
46
|
+
Statement.new(statement).cohort_analysis?
|
47
|
+
end
|
48
|
+
|
49
|
+
def statement_object
|
50
|
+
Statement.new(statement, data_source)
|
45
51
|
end
|
46
52
|
end
|
47
53
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
<% if @bind_vars.any? %>
|
2
|
+
<% var_params = request.query_parameters %>
|
2
3
|
<script>
|
3
4
|
<%= blazer_js_var "timeZone", Blazer.time_zone.tzinfo.name %>
|
4
5
|
var now = moment.tz(timeZone)
|
@@ -19,14 +20,14 @@
|
|
19
20
|
<% @bind_vars.each_with_index do |var, i| %>
|
20
21
|
<%= label_tag var, var %>
|
21
22
|
<% if (data = @smart_vars[var]) %>
|
22
|
-
<%= select_tag var, options_for_select([[nil, nil]] + data, selected:
|
23
|
+
<%= select_tag var, options_for_select([[nil, nil]] + data, selected: var_params[var]), style: "margin-right: 20px; width: 200px; display: none;" %>
|
23
24
|
<script>
|
24
25
|
$("#<%= var %>").selectize({
|
25
26
|
create: true
|
26
27
|
});
|
27
28
|
</script>
|
28
29
|
<% elsif var.end_with?("_at") || var == "start_time" || var == "end_time" %>
|
29
|
-
<%= hidden_field_tag var,
|
30
|
+
<%= hidden_field_tag var, var_params[var] %>
|
30
31
|
|
31
32
|
<div class="selectize-control single" style="width: 200px;">
|
32
33
|
<div id="<%= var %>-select" class="selectize-input" style="display: inline-block;">
|
@@ -58,13 +59,13 @@
|
|
58
59
|
})()
|
59
60
|
</script>
|
60
61
|
<% else %>
|
61
|
-
<%= text_field_tag var,
|
62
|
+
<%= text_field_tag var, var_params[var], style: "width: 120px; margin-right: 20px;", autofocus: i == 0 && !var.end_with?("_at") && !var_params[var], class: "form-control" %>
|
62
63
|
<% end %>
|
63
64
|
<% end %>
|
64
65
|
|
65
66
|
<% if date_vars %>
|
66
67
|
<% date_vars.each do |var| %>
|
67
|
-
<%= hidden_field_tag var,
|
68
|
+
<%= hidden_field_tag var, var_params[var] %>
|
68
69
|
<% end %>
|
69
70
|
|
70
71
|
<%= label_tag nil, date_vars.join(" & ") %>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, variable_params(@dashboard)) : dashboards_path(variable_params(@dashboard))), html: {id: "app", class: "small-form"} do |f| %>
|
1
|
+
<%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, params: variable_params(@dashboard)) : dashboards_path(params: variable_params(@dashboard))), html: {id: "app", class: "small-form"} do |f| %>
|
2
2
|
<% if @dashboard.errors.any? %>
|
3
3
|
<div class="alert alert-danger"><%= @dashboard.errors.full_messages.first %></div>
|
4
4
|
<% end %>
|
@@ -10,7 +10,7 @@
|
|
10
10
|
</h3>
|
11
11
|
</div>
|
12
12
|
<div class="col-sm-3 text-right">
|
13
|
-
<%= link_to "Edit", edit_dashboard_path(@dashboard, variable_params(@dashboard)), class: "btn btn-info" %>
|
13
|
+
<%= link_to "Edit", edit_dashboard_path(@dashboard, params: variable_params(@dashboard)), class: "btn btn-info" %>
|
14
14
|
</div>
|
15
15
|
</div>
|
16
16
|
</div>
|
@@ -21,7 +21,7 @@
|
|
21
21
|
<% if @data_sources.any? { |ds| ds.cache_mode != "off" } %>
|
22
22
|
<p class="text-muted" style="float: right;">
|
23
23
|
Some queries may be cached
|
24
|
-
<%= link_to "Refresh", refresh_dashboard_path(@dashboard, variable_params(@dashboard)), method: :post %>
|
24
|
+
<%= link_to "Refresh", refresh_dashboard_path(@dashboard, params: variable_params(@dashboard)), method: :post %>
|
25
25
|
</p>
|
26
26
|
<% end %>
|
27
27
|
|
@@ -33,13 +33,15 @@
|
|
33
33
|
|
34
34
|
<% @queries.each_with_index do |query, i| %>
|
35
35
|
<div class="chart-container">
|
36
|
-
<h4><%= link_to query.friendly_name, query_path(query, variable_params(query)), target: "_blank" %></h4>
|
36
|
+
<h4><%= link_to query.friendly_name, query_path(query, params: variable_params(query)), target: "_blank" %></h4>
|
37
37
|
<div id="chart-<%= i %>" class="chart">
|
38
38
|
<p class="text-muted">Loading...</p>
|
39
39
|
</div>
|
40
40
|
</div>
|
41
41
|
<script>
|
42
|
-
|
42
|
+
<% data = {statement: query.statement, query_id: query.id, data_source: query.data_source, variables: variable_params(query), only_chart: true} %>
|
43
|
+
<% data.merge!(cohort_period: params[:cohort_period]) if params[:cohort_period] %>
|
44
|
+
<%= blazer_js_var "data", data %>
|
43
45
|
|
44
46
|
runQuery(data, function (data) {
|
45
47
|
$("#chart-<%= i %>").html(data)
|
@@ -10,7 +10,7 @@
|
|
10
10
|
<% end %>
|
11
11
|
|
12
12
|
<% if @query && params[:query_id] %>
|
13
|
-
<%= link_to "Refresh", refresh_query_path(@query, variable_params(@query)), method: :post %>
|
13
|
+
<%= link_to "Refresh", refresh_query_path(@query, params: variable_params(@query, @var_params)), method: :post %>
|
14
14
|
<% end %>
|
15
15
|
</p>
|
16
16
|
<% end %>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<% end %>
|
4
4
|
|
5
5
|
<div id="app" v-cloak>
|
6
|
-
<%= form_for @query, url: (@query.persisted? ? query_path(@query, variable_params(@query)) : queries_path(variable_params(@query))), html: {autocomplete: "off"} do |f| %>
|
6
|
+
<%= form_for @query, url: (@query.persisted? ? query_path(@query, params: variable_params(@query)) : queries_path(params: variable_params(@query))), html: {autocomplete: "off"} do |f| %>
|
7
7
|
<div class="row">
|
8
8
|
<div id="statement-box" class="col-xs-8">
|
9
9
|
<div class= "form-group">
|
@@ -67,7 +67,7 @@
|
|
67
67
|
</div>
|
68
68
|
|
69
69
|
<script>
|
70
|
-
<%= blazer_js_var "
|
70
|
+
<%= blazer_js_var "variableParams", variable_params(@query) %>
|
71
71
|
<%= blazer_js_var "previewStatement", Hash[Blazer.data_sources.map { |k, v| [k, (v.preview_statement rescue "")] }] %>
|
72
72
|
|
73
73
|
var app = new Vue({
|
@@ -95,7 +95,7 @@
|
|
95
95
|
this.error = false
|
96
96
|
cancelAllQueries()
|
97
97
|
|
98
|
-
var data =
|
98
|
+
var data = {statement: this.getSQL(), data_source: $("#query_data_source").val(), variables: variableParams}
|
99
99
|
|
100
100
|
var _this = this
|
101
101
|
|
@@ -31,7 +31,7 @@
|
|
31
31
|
|
32
32
|
<% if @query && @result.forecastable? && !params[:forecast] %>
|
33
33
|
·
|
34
|
-
<%= link_to "Forecast", query_path(@query, {forecast: "t"}.merge(variable_params(@query))) %>
|
34
|
+
<%= link_to "Forecast", query_path(@query, params: {forecast: "t"}.merge(variable_params(@query))) %>
|
35
35
|
<% end %>
|
36
36
|
</p>
|
37
37
|
<% end %>
|
@@ -1,5 +1,12 @@
|
|
1
1
|
<% blazer_title @query.name %>
|
2
2
|
|
3
|
+
<% if @success %>
|
4
|
+
<% run_data = {statement: @query.statement, query_id: @query.id, data_source: @query.data_source, variables: variable_params(@query)} %>
|
5
|
+
<% run_data.merge!(forecast: "t") if params[:forecast] %>
|
6
|
+
<% run_data.merge!(cohort_period: params[:cohort_period]) if params[:cohort_period] %>
|
7
|
+
<% run_data.transform_keys!(&:to_s) if Rails::VERSION::STRING.to_f == 5.0 %>
|
8
|
+
<% end %>
|
9
|
+
|
3
10
|
<div class="topbar">
|
4
11
|
<div class="container">
|
5
12
|
<div class="row" style="padding-top: 13px;">
|
@@ -10,11 +17,11 @@
|
|
10
17
|
</h3>
|
11
18
|
</div>
|
12
19
|
<div class="col-sm-3 text-right">
|
13
|
-
<%= link_to "Edit", edit_query_path(@query, variable_params(@query)), class: "btn btn-default", disabled: !@query.editable?(blazer_user) %>
|
14
|
-
<%= link_to "Fork", new_query_path(variable_params(@query).merge(fork_query_id: @query.id, data_source: @query.data_source, name: @query.name)), class: "btn btn-info" %>
|
20
|
+
<%= link_to "Edit", edit_query_path(@query, params: variable_params(@query)), class: "btn btn-default", disabled: !@query.editable?(blazer_user) %>
|
21
|
+
<%= link_to "Fork", new_query_path(params: variable_params(@query).merge(fork_query_id: @query.id, data_source: @query.data_source, name: @query.name)), class: "btn btn-info" %>
|
15
22
|
|
16
23
|
<% if !@error && @success %>
|
17
|
-
<%= button_to "Download", run_queries_path(
|
24
|
+
<%= button_to "Download", run_queries_path(format: "csv"), params: run_data, class: "btn btn-primary" %>
|
18
25
|
<% end %>
|
19
26
|
</div>
|
20
27
|
</div>
|
@@ -39,7 +46,7 @@
|
|
39
46
|
|
40
47
|
<%= render partial: "blazer/variables", locals: {action: query_path(@query)} %>
|
41
48
|
|
42
|
-
<pre id="code"><code><%= @statement %></code></pre>
|
49
|
+
<pre id="code"><code><%= @statement.display_statement %></code></pre>
|
43
50
|
|
44
51
|
<% if @success %>
|
45
52
|
<div id="results">
|
@@ -56,9 +63,7 @@
|
|
56
63
|
$("#results").addClass("query-error").html(message)
|
57
64
|
}
|
58
65
|
|
59
|
-
|
60
|
-
<% data.merge!(forecast: "t") if params[:forecast] %>
|
61
|
-
<%= blazer_js_var "data", data %>
|
66
|
+
<%= blazer_js_var "data", run_data %>
|
62
67
|
|
63
68
|
runQuery(data, showRun, showError)
|
64
69
|
</script>
|
@@ -5,8 +5,13 @@
|
|
5
5
|
|
6
6
|
<meta charset="utf-8" />
|
7
7
|
<%= favicon_link_tag "blazer/favicon.png" %>
|
8
|
-
|
9
|
-
|
8
|
+
<% if defined?(Propshaft::Railtie) %>
|
9
|
+
<%= stylesheet_link_tag "blazer/bootstrap-propshaft", "blazer/bootstrap", "blazer/selectize", "blazer/github", "blazer/daterangepicker", "blazer/application" %>
|
10
|
+
<%= javascript_include_tag "blazer/jquery", "blazer/jquery-ujs", "blazer/stupidtable", "blazer/stupidtable-custom-settings", "blazer/jquery.stickytableheaders", "blazer/selectize", "blazer/highlight.min", "blazer/moment", "blazer/moment-timezone-with-data", "blazer/daterangepicker", "blazer/Chart.js", "blazer/chartkick", "blazer/ace/ace", "blazer/ace/ext-language_tools", "blazer/ace/theme-twilight", "blazer/ace/mode-sql", "blazer/ace/snippets/text", "blazer/ace/snippets/sql", "blazer/Sortable", "blazer/bootstrap", "blazer/vue", "blazer/routes", "blazer/queries", "blazer/fuzzysearch", "blazer/application" %>
|
11
|
+
<% else %>
|
12
|
+
<%= stylesheet_link_tag "blazer/application" %>
|
13
|
+
<%= javascript_include_tag "blazer/application" %>
|
14
|
+
<% end %>
|
10
15
|
<script>
|
11
16
|
<%= blazer_js_var "rootPath", root_path %>
|
12
17
|
</script>
|
@@ -1,31 +1,50 @@
|
|
1
1
|
module Blazer
|
2
2
|
module Adapters
|
3
3
|
class AthenaAdapter < BaseAdapter
|
4
|
-
def run_statement(statement, comment)
|
4
|
+
def run_statement(statement, comment, bind_params = [])
|
5
5
|
require "digest/md5"
|
6
6
|
|
7
7
|
columns = []
|
8
8
|
rows = []
|
9
9
|
error = nil
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
begin
|
12
|
+
# use empty? since any? doesn't work for [nil]
|
13
|
+
if !bind_params.empty?
|
14
|
+
request_token = Digest::MD5.hexdigest([statement, bind_params.to_json, data_source.id, settings["workgroup"]].compact.join("/"))
|
15
|
+
statement_name = "blazer_#{request_token}"
|
16
|
+
begin
|
17
|
+
client.create_prepared_statement({
|
18
|
+
statement_name: statement_name,
|
19
|
+
work_group: settings["workgroup"],
|
20
|
+
query_statement: statement
|
21
|
+
})
|
22
|
+
rescue Aws::Athena::Errors::InvalidRequestException => e
|
23
|
+
raise e unless e.message.include?("already exists in WorkGroup")
|
24
|
+
end
|
25
|
+
using_statement = bind_params.map { |v| data_source.quote(v) }.join(", ")
|
26
|
+
statement = "EXECUTE #{statement_name} USING #{using_statement}"
|
27
|
+
else
|
28
|
+
request_token = Digest::MD5.hexdigest([statement, data_source.id, settings["workgroup"]].compact.join("/"))
|
29
|
+
end
|
30
|
+
|
31
|
+
query_options = {
|
32
|
+
query_string: statement,
|
33
|
+
# use token so we fetch cached results after query is run
|
34
|
+
client_request_token: request_token,
|
35
|
+
query_execution_context: {
|
36
|
+
database: database,
|
37
|
+
}
|
17
38
|
}
|
18
|
-
}
|
19
39
|
|
20
|
-
|
21
|
-
|
22
|
-
|
40
|
+
if settings["output_location"]
|
41
|
+
query_options[:result_configuration] = {output_location: settings["output_location"]}
|
42
|
+
end
|
23
43
|
|
24
|
-
|
25
|
-
|
26
|
-
|
44
|
+
if settings["workgroup"]
|
45
|
+
query_options[:work_group] = settings["workgroup"]
|
46
|
+
end
|
27
47
|
|
28
|
-
begin
|
29
48
|
resp = client.start_query_execution(**query_options)
|
30
49
|
query_execution_id = resp.query_execution_id
|
31
50
|
|
@@ -111,12 +130,29 @@ module Blazer
|
|
111
130
|
"SELECT * FROM {table} LIMIT 10"
|
112
131
|
end
|
113
132
|
|
133
|
+
# https://docs.aws.amazon.com/athena/latest/ug/select.html#select-escaping
|
134
|
+
def quoting
|
135
|
+
:single_quote_escape
|
136
|
+
end
|
137
|
+
|
138
|
+
# https://docs.aws.amazon.com/athena/latest/ug/querying-with-prepared-statements.html
|
139
|
+
def parameter_binding
|
140
|
+
engine_version > 1 ? :positional : nil
|
141
|
+
end
|
142
|
+
|
114
143
|
private
|
115
144
|
|
116
145
|
def database
|
117
146
|
@database ||= settings["database"] || "default"
|
118
147
|
end
|
119
148
|
|
149
|
+
# note: this setting is experimental
|
150
|
+
# it does *not* need to be set to use engine version 2
|
151
|
+
# prepared statements must be manually deleted if enabled
|
152
|
+
def engine_version
|
153
|
+
@engine_version ||= (settings["engine_version"] || 1).to_i
|
154
|
+
end
|
155
|
+
|
120
156
|
def fetch_error(query_execution_id)
|
121
157
|
client.get_query_execution(
|
122
158
|
query_execution_id: query_execution_id
|
@@ -133,11 +169,12 @@ module Blazer
|
|
133
169
|
|
134
170
|
def client_options
|
135
171
|
@client_options ||= begin
|
172
|
+
options = {}
|
136
173
|
if settings["access_key_id"] || settings["secret_access_key"]
|
137
|
-
|
138
|
-
else
|
139
|
-
{}
|
174
|
+
options[:credentials] = Aws::Credentials.new(settings["access_key_id"], settings["secret_access_key"])
|
140
175
|
end
|
176
|
+
options[:region] = settings["region"] if settings["region"]
|
177
|
+
options
|
141
178
|
end
|
142
179
|
end
|
143
180
|
end
|
@@ -8,7 +8,22 @@ module Blazer
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def run_statement(statement, comment)
|
11
|
-
#
|
11
|
+
# required
|
12
|
+
end
|
13
|
+
|
14
|
+
def quoting
|
15
|
+
# required, how to quote variables
|
16
|
+
# :backslash_escape - single quote strings and convert ' to \' and \ to \\
|
17
|
+
# :single_quote_escape - single quote strings and convert ' to ''
|
18
|
+
# ->(value) { ... } - custom method
|
19
|
+
end
|
20
|
+
|
21
|
+
def parameter_binding
|
22
|
+
# optional, but recommended when possible for security
|
23
|
+
# if specified, quoting is only used for display
|
24
|
+
# :positional - ?
|
25
|
+
# :numeric - $1
|
26
|
+
# ->(statement, values) { ... } - custom method
|
12
27
|
end
|
13
28
|
|
14
29
|
def tables
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module Blazer
|
2
2
|
module Adapters
|
3
3
|
class BigQueryAdapter < BaseAdapter
|
4
|
-
def run_statement(statement, comment)
|
4
|
+
def run_statement(statement, comment, bind_params)
|
5
5
|
columns = []
|
6
6
|
rows = []
|
7
7
|
error = nil
|
8
8
|
|
9
9
|
begin
|
10
|
-
results = bigquery.query(statement)
|
10
|
+
results = bigquery.query(statement, params: bind_params)
|
11
11
|
|
12
12
|
# complete? was removed in google-cloud-bigquery 0.29.0
|
13
13
|
# code is for backward compatibility
|
@@ -19,6 +19,7 @@ module Blazer
|
|
19
19
|
end
|
20
20
|
rescue => e
|
21
21
|
error = e.message
|
22
|
+
error = Blazer::VARIABLE_MESSAGE if error.include?("Syntax error: Unexpected \"?\"")
|
22
23
|
end
|
23
24
|
|
24
25
|
[columns, rows, error]
|
@@ -42,6 +43,16 @@ module Blazer
|
|
42
43
|
"SELECT * FROM `{table}` LIMIT 10"
|
43
44
|
end
|
44
45
|
|
46
|
+
# https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#string_and_bytes_literals
|
47
|
+
def quoting
|
48
|
+
:backslash_escape
|
49
|
+
end
|
50
|
+
|
51
|
+
# https://cloud.google.com/bigquery/docs/parameterized-queries
|
52
|
+
def parameter_binding
|
53
|
+
:positional
|
54
|
+
end
|
55
|
+
|
45
56
|
private
|
46
57
|
|
47
58
|
def bigquery
|
@@ -1,28 +1,29 @@
|
|
1
1
|
module Blazer
|
2
2
|
module Adapters
|
3
3
|
class CassandraAdapter < BaseAdapter
|
4
|
-
def run_statement(statement, comment)
|
4
|
+
def run_statement(statement, comment, bind_params)
|
5
5
|
columns = []
|
6
6
|
rows = []
|
7
7
|
error = nil
|
8
8
|
|
9
9
|
begin
|
10
|
-
response = session.execute("#{statement} /*#{comment}*/")
|
10
|
+
response = session.execute("#{statement} /*#{comment}*/", arguments: bind_params)
|
11
11
|
rows = response.map { |r| r.values }
|
12
12
|
columns = rows.any? ? response.first.keys : []
|
13
13
|
rescue => e
|
14
14
|
error = e.message
|
15
|
+
error = Blazer::VARIABLE_MESSAGE if error.include?("no viable alternative at input '?'")
|
15
16
|
end
|
16
17
|
|
17
18
|
[columns, rows, error]
|
18
19
|
end
|
19
20
|
|
20
21
|
def tables
|
21
|
-
session.execute("SELECT table_name FROM system_schema.tables WHERE keyspace_name =
|
22
|
+
session.execute("SELECT table_name FROM system_schema.tables WHERE keyspace_name = #{data_source.quote(keyspace)}").map { |r| r["table_name"] }
|
22
23
|
end
|
23
24
|
|
24
25
|
def schema
|
25
|
-
result = session.execute("SELECT keyspace_name, table_name, column_name, type, position FROM system_schema.columns WHERE keyspace_name =
|
26
|
+
result = session.execute("SELECT keyspace_name, table_name, column_name, type, position FROM system_schema.columns WHERE keyspace_name = #{data_source.quote(keyspace)}")
|
26
27
|
result.map(&:values).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]} }} }
|
27
28
|
end
|
28
29
|
|
@@ -30,6 +31,16 @@ module Blazer
|
|
30
31
|
"SELECT * FROM {table} LIMIT 10"
|
31
32
|
end
|
32
33
|
|
34
|
+
# https://docs.datastax.com/en/cql-oss/3.3/cql/cql_reference/escape_char_r.html
|
35
|
+
def quoting
|
36
|
+
:single_quote_escape
|
37
|
+
end
|
38
|
+
|
39
|
+
# https://docs.datastax.com/en/developer/nodejs-driver/3.0/features/parameterized-queries/
|
40
|
+
def parameter_binding
|
41
|
+
:positional
|
42
|
+
end
|
43
|
+
|
33
44
|
private
|
34
45
|
|
35
46
|
def cluster
|
@@ -18,6 +18,16 @@ module Blazer
|
|
18
18
|
[columns, rows, error]
|
19
19
|
end
|
20
20
|
|
21
|
+
# https://drill.apache.org/docs/lexical-structure/#string
|
22
|
+
def quoting
|
23
|
+
:single_quote_escape
|
24
|
+
end
|
25
|
+
|
26
|
+
# https://issues.apache.org/jira/browse/DRILL-5079
|
27
|
+
def parameter_binding
|
28
|
+
# not supported
|
29
|
+
end
|
30
|
+
|
21
31
|
private
|
22
32
|
|
23
33
|
def drill
|