blazer 2.5.0 → 2.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +63 -15
  4. data/app/assets/javascripts/blazer/queries.js +12 -1
  5. data/app/assets/stylesheets/blazer/application.css +1 -0
  6. data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
  7. data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
  8. data/app/assets/stylesheets/blazer/{bootstrap.css.erb → bootstrap.css} +0 -6
  9. data/app/controllers/blazer/base_controller.rb +45 -45
  10. data/app/controllers/blazer/dashboards_controller.rb +4 -11
  11. data/app/controllers/blazer/queries_controller.rb +29 -48
  12. data/app/models/blazer/query.rb +8 -2
  13. data/app/views/blazer/_variables.html.erb +5 -4
  14. data/app/views/blazer/dashboards/_form.html.erb +1 -1
  15. data/app/views/blazer/dashboards/show.html.erb +6 -4
  16. data/app/views/blazer/queries/_caching.html.erb +1 -1
  17. data/app/views/blazer/queries/_form.html.erb +3 -3
  18. data/app/views/blazer/queries/run.html.erb +1 -1
  19. data/app/views/blazer/queries/show.html.erb +12 -7
  20. data/app/views/layouts/blazer/application.html.erb +7 -2
  21. data/lib/blazer/adapters/athena_adapter.rb +55 -18
  22. data/lib/blazer/adapters/base_adapter.rb +16 -1
  23. data/lib/blazer/adapters/bigquery_adapter.rb +13 -2
  24. data/lib/blazer/adapters/cassandra_adapter.rb +15 -4
  25. data/lib/blazer/adapters/drill_adapter.rb +10 -0
  26. data/lib/blazer/adapters/druid_adapter.rb +36 -1
  27. data/lib/blazer/adapters/elasticsearch_adapter.rb +13 -2
  28. data/lib/blazer/adapters/hive_adapter.rb +10 -0
  29. data/lib/blazer/adapters/ignite_adapter.rb +12 -2
  30. data/lib/blazer/adapters/influxdb_adapter.rb +22 -10
  31. data/lib/blazer/adapters/mongodb_adapter.rb +4 -0
  32. data/lib/blazer/adapters/neo4j_adapter.rb +17 -2
  33. data/lib/blazer/adapters/opensearch_adapter.rb +4 -0
  34. data/lib/blazer/adapters/presto_adapter.rb +9 -0
  35. data/lib/blazer/adapters/salesforce_adapter.rb +5 -0
  36. data/lib/blazer/adapters/snowflake_adapter.rb +9 -0
  37. data/lib/blazer/adapters/soda_adapter.rb +9 -0
  38. data/lib/blazer/adapters/spark_adapter.rb +5 -0
  39. data/lib/blazer/adapters/sql_adapter.rb +37 -3
  40. data/lib/blazer/data_source.rb +85 -5
  41. data/lib/blazer/engine.rb +0 -4
  42. data/lib/blazer/result.rb +2 -0
  43. data/lib/blazer/run_statement.rb +7 -3
  44. data/lib/blazer/run_statement_job.rb +4 -2
  45. data/lib/blazer/slack_notifier.rb +5 -2
  46. data/lib/blazer/statement.rb +75 -0
  47. data/lib/blazer/version.rb +1 -1
  48. data/lib/blazer.rb +14 -6
  49. 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.statement.dup
63
- process_vars(@statement, @query.data_source)
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(@data_source, @statement, options)
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
- data_source = Blazer.data_sources[@query.data_source]
170
- @statement = @query.statement.dup
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
@@ -35,13 +35,19 @@ module Blazer
35
35
  end
36
36
 
37
37
  def variables
38
- variables = Blazer.extract_vars(statement)
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
- /\/\*\s*cohort analysis\s*\*\//i.match?(statement)
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: params[var]), style: "margin-right: 20px; width: 200px; display: none;" %>
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, params[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, params[var], style: "width: 120px; margin-right: 20px;", autofocus: i == 0 && !var.end_with?("_at") && !params[var], class: "form-control" %>
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, params[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
- <%= blazer_js_var "data", {statement: @statements[i], query_id: query.id, data_source: query.data_source, only_chart: true, cohort_period: params[:cohort_period]} %>
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 "params", variable_params(@query) %>
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 = $.extend({}, params, {statement: this.getSQL(), data_source: $("#query_data_source").val()})
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
  &middot;
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(query_id: @query.id, format: "csv", forecast: params[:forecast], cohort_period: params[:cohort_period]), params: {statement: @statement}, class: "btn btn-primary" %>
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
- <% data = variable_params(@query).merge(statement: @statement, query_id: @query.id, data_source: @query.data_source) %>
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
- <%= stylesheet_link_tag "blazer/application" %>
9
- <%= javascript_include_tag "blazer/application" %>
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
- query_options = {
12
- query_string: statement,
13
- # use token so we fetch cached results after query is run
14
- client_request_token: Digest::MD5.hexdigest([statement, data_source.id, settings["workgroup"]].compact.join("/")),
15
- query_execution_context: {
16
- database: database,
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
- if settings["output_location"]
21
- query_options[:result_configuration] = {output_location: settings["output_location"]}
22
- end
40
+ if settings["output_location"]
41
+ query_options[:result_configuration] = {output_location: settings["output_location"]}
42
+ end
23
43
 
24
- if settings["workgroup"]
25
- query_options[:work_group] = settings["workgroup"]
26
- end
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
- {credentials: Aws::Credentials.new(settings["access_key_id"], settings["secret_access_key"])}
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
- # the one required method
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 = '#{keyspace}'").map { |r| r["table_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 = '#{keyspace}'")
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