blazer 2.5.0 → 2.6.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +55 -9
  4. data/app/assets/stylesheets/blazer/application.css +1 -0
  5. data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
  6. data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
  7. data/app/assets/stylesheets/blazer/{bootstrap.css.erb → bootstrap.css} +0 -6
  8. data/app/controllers/blazer/base_controller.rb +45 -45
  9. data/app/controllers/blazer/dashboards_controller.rb +4 -11
  10. data/app/controllers/blazer/queries_controller.rb +28 -48
  11. data/app/models/blazer/query.rb +8 -2
  12. data/app/views/blazer/_variables.html.erb +5 -4
  13. data/app/views/blazer/dashboards/_form.html.erb +1 -1
  14. data/app/views/blazer/dashboards/show.html.erb +4 -4
  15. data/app/views/blazer/queries/_caching.html.erb +1 -1
  16. data/app/views/blazer/queries/_form.html.erb +3 -3
  17. data/app/views/blazer/queries/run.html.erb +1 -1
  18. data/app/views/blazer/queries/show.html.erb +12 -7
  19. data/app/views/layouts/blazer/application.html.erb +7 -2
  20. data/lib/blazer/adapters/athena_adapter.rb +51 -15
  21. data/lib/blazer/adapters/base_adapter.rb +16 -1
  22. data/lib/blazer/adapters/bigquery_adapter.rb +13 -2
  23. data/lib/blazer/adapters/cassandra_adapter.rb +15 -4
  24. data/lib/blazer/adapters/drill_adapter.rb +10 -0
  25. data/lib/blazer/adapters/druid_adapter.rb +36 -1
  26. data/lib/blazer/adapters/elasticsearch_adapter.rb +13 -2
  27. data/lib/blazer/adapters/hive_adapter.rb +10 -0
  28. data/lib/blazer/adapters/ignite_adapter.rb +12 -2
  29. data/lib/blazer/adapters/influxdb_adapter.rb +22 -10
  30. data/lib/blazer/adapters/mongodb_adapter.rb +4 -0
  31. data/lib/blazer/adapters/neo4j_adapter.rb +17 -2
  32. data/lib/blazer/adapters/opensearch_adapter.rb +4 -0
  33. data/lib/blazer/adapters/presto_adapter.rb +9 -0
  34. data/lib/blazer/adapters/salesforce_adapter.rb +5 -0
  35. data/lib/blazer/adapters/snowflake_adapter.rb +9 -0
  36. data/lib/blazer/adapters/soda_adapter.rb +9 -0
  37. data/lib/blazer/adapters/spark_adapter.rb +5 -0
  38. data/lib/blazer/adapters/sql_adapter.rb +30 -3
  39. data/lib/blazer/data_source.rb +85 -5
  40. data/lib/blazer/engine.rb +0 -4
  41. data/lib/blazer/run_statement.rb +7 -3
  42. data/lib/blazer/run_statement_job.rb +4 -2
  43. data/lib/blazer/slack_notifier.rb +5 -2
  44. data/lib/blazer/statement.rb +75 -0
  45. data/lib/blazer/version.rb +1 -1
  46. data/lib/blazer.rb +14 -6
  47. metadata +7 -4
@@ -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,13 @@
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
+ <%= blazer_js_var "data", {statement: query.statement, query_id: query.id, variables: variable_params(query), only_chart: true, cohort_period: params[:cohort_period]} %>
43
43
 
44
44
  runQuery(data, function (data) {
45
45
  $("#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, 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
@@ -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
@@ -3,15 +3,37 @@ module Blazer
3
3
  class DruidAdapter < BaseAdapter
4
4
  TIMESTAMP_REGEX = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\z/
5
5
 
6
- def run_statement(statement, comment)
6
+ def run_statement(statement, comment, bind_params)
7
+ require "json"
8
+ require "net/http"
9
+ require "uri"
10
+
7
11
  columns = []
8
12
  rows = []
9
13
  error = nil
10
14
 
15
+ params =
16
+ bind_params.map do |v|
17
+ type =
18
+ case v
19
+ when Integer
20
+ "BIGINT"
21
+ when Float
22
+ "DOUBLE"
23
+ when ActiveSupport::TimeWithZone
24
+ v = (v.to_f * 1000).round
25
+ "TIMESTAMP"
26
+ else
27
+ "VARCHAR"
28
+ end
29
+ {type: type, value: v}
30
+ end
31
+
11
32
  header = {"Content-Type" => "application/json", "Accept" => "application/json"}
12
33
  timeout = data_source.timeout ? data_source.timeout.to_i : 300
13
34
  data = {
14
35
  query: statement,
36
+ parameters: params,
15
37
  context: {
16
38
  timeout: timeout * 1000
17
39
  }
@@ -27,6 +49,8 @@ module Blazer
27
49
  error = response["errorMessage"] || "Unknown error: #{response.inspect}"
28
50
  if error.include?("timed out")
29
51
  error = Blazer::TIMEOUT_MESSAGE
52
+ elsif error.include?("Encountered \"?\" at")
53
+ error = Blazer::VARIABLE_MESSAGE
30
54
  end
31
55
  else
32
56
  columns = (response.first || {}).keys
@@ -62,6 +86,17 @@ module Blazer
62
86
  def preview_statement
63
87
  "SELECT * FROM {table} LIMIT 10"
64
88
  end
89
+
90
+ # https://druid.apache.org/docs/latest/querying/sql.html#identifiers-and-literals
91
+ # docs only mention double quotes
92
+ def quoting
93
+ :single_quote_escape
94
+ end
95
+
96
+ # https://druid.apache.org/docs/latest/querying/sql.html#dynamic-parameters
97
+ def parameter_binding
98
+ :positional
99
+ end
65
100
  end
66
101
  end
67
102
  end
@@ -1,13 +1,13 @@
1
1
  module Blazer
2
2
  module Adapters
3
3
  class ElasticsearchAdapter < 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 = client.transport.perform_request("POST", endpoint, {}, {query: "#{statement} /*#{comment}*/"}).body
10
+ response = client.transport.perform_request("POST", endpoint, {}, {query: "#{statement} /*#{comment}*/", params: bind_params}).body
11
11
  columns = response["columns"].map { |v| v["name"] }
12
12
  # Elasticsearch does not differentiate between dates and times
13
13
  date_indexes = response["columns"].each_index.select { |i| ["date", "datetime"].include?(response["columns"][i]["type"]) }
@@ -21,6 +21,7 @@ module Blazer
21
21
  end
22
22
  rescue => e
23
23
  error = e.message
24
+ error = Blazer::VARIABLE_MESSAGE if error.include?("mismatched input '?'")
24
25
  end
25
26
 
26
27
  [columns, rows, error]
@@ -36,6 +37,16 @@ module Blazer
36
37
  "SELECT * FROM \"{table}\" LIMIT 10"
37
38
  end
38
39
 
40
+ # https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-lexical-structure.html#sql-syntax-string-literals
41
+ def quoting
42
+ :single_quote_escape
43
+ end
44
+
45
+ # https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-rest-params.html
46
+ def parameter_binding
47
+ :positional
48
+ end
49
+
39
50
  protected
40
51
 
41
52
  def endpoint
@@ -25,6 +25,16 @@ module Blazer
25
25
  "SELECT * FROM {table} LIMIT 10"
26
26
  end
27
27
 
28
+ # https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types#LanguageManualTypes-StringsstringStrings
29
+ def quoting
30
+ :backslash_escape
31
+ end
32
+
33
+ # has variable substitution, but sets for session
34
+ # https://cwiki.apache.org/confluence/display/Hive/LanguageManual+VariableSubstitution
35
+ def parameter_binding
36
+ end
37
+
28
38
  protected
29
39
 
30
40
  def client
@@ -1,13 +1,13 @@
1
1
  module Blazer
2
2
  module Adapters
3
3
  class IgniteAdapter < 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
- result = client.query("#{statement} /*#{comment}*/", schema: default_schema, statement_type: :select, timeout: data_source.timeout)
10
+ result = client.query("#{statement} /*#{comment}*/", bind_params, schema: default_schema, statement_type: :select, timeout: data_source.timeout)
11
11
  columns = result.any? ? result.first.keys : []
12
12
  rows = result.map(&:values)
13
13
  rescue => e
@@ -37,6 +37,16 @@ module Blazer
37
37
  # result.rows.group_by { |r| [r[0], r[1]] }.map { |k, vs| {schema: k[0], table: k[1], columns: vs.sort_by { |v| v[2] }.map { |v| {name: v[2], data_type: v[3]} }} }.sort_by { |t| [t[:schema] == default_schema ? "" : t[:schema], t[:table]] }
38
38
  # end
39
39
 
40
+ def quoting
41
+ :single_quote_escape
42
+ end
43
+
44
+ # query arguments
45
+ # https://ignite.apache.org/docs/latest/binary-client-protocol/sql-and-scan-queries#op_query_sql
46
+ def parameter_binding
47
+ :positional
48
+ end
49
+
40
50
  private
41
51
 
42
52
  def default_schema
@@ -8,16 +8,19 @@ module Blazer
8
8
 
9
9
  begin
10
10
  result = client.query(statement, denormalize: false).first
11
- columns = result["columns"]
12
- rows = result["values"]
13
-
14
- # parse time columns
15
- # current approach isn't ideal, but result doesn't include types
16
- # another approach would be to check the format
17
- time_index = columns.index("time")
18
- if time_index
19
- rows.each do |row|
20
- row[time_index] = Time.parse(row[time_index]) if row[time_index]
11
+
12
+ if result
13
+ columns = result["columns"]
14
+ rows = result["values"]
15
+
16
+ # parse time columns
17
+ # current approach isn't ideal, but result doesn't include types
18
+ # another approach would be to check the format
19
+ time_index = columns.index("time")
20
+ if time_index
21
+ rows.each do |row|
22
+ row[time_index] = Time.parse(row[time_index]) if row[time_index]
23
+ end
21
24
  end
22
25
  end
23
26
  rescue => e
@@ -35,6 +38,15 @@ module Blazer
35
38
  "SELECT * FROM {table} LIMIT 10"
36
39
  end
37
40
 
41
+ # https://docs.influxdata.com/influxdb/v1.8/query_language/spec/#strings
42
+ def quoting
43
+ :backslash_escape
44
+ end
45
+
46
+ def parameter_binding
47
+ # not supported
48
+ end
49
+
38
50
  protected
39
51
 
40
52
  def client
@@ -25,6 +25,10 @@ module Blazer
25
25
  "db.{table}.find().limit(10)"
26
26
  end
27
27
 
28
+ def quoting
29
+ :backslash_escape
30
+ end
31
+
28
32
  protected
29
33
 
30
34
  def client
@@ -1,13 +1,13 @@
1
1
  module Blazer
2
2
  module Adapters
3
3
  class Neo4jAdapter < 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
- result = session.query("#{statement} /*#{comment}*/")
10
+ result = session.query("#{statement} /*#{comment}*/", bind_params)
11
11
  columns = result.columns.map(&:to_s)
12
12
  rows = []
13
13
  result.each do |row|
@@ -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?("Invalid input '$'")
22
23
  end
23
24
 
24
25
  [columns, rows, error]
@@ -33,6 +34,20 @@ module Blazer
33
34
  "MATCH (n:{table}) RETURN n LIMIT 10"
34
35
  end
35
36
 
37
+ # https://neo4j.com/docs/cypher-manual/current/syntax/expressions/#cypher-expressions-string-literals
38
+ def quoting
39
+ :backslash_escape
40
+ end
41
+
42
+ def parameter_binding
43
+ proc do |statement, variables|
44
+ variables.each_key do |k|
45
+ statement = statement.gsub("{#{k}}") { "$#{k} " }
46
+ end
47
+ [statement, variables]
48
+ end
49
+ end
50
+
36
51
  protected
37
52
 
38
53
  def session
@@ -38,6 +38,10 @@ module Blazer
38
38
  "SELECT * FROM `{table}` LIMIT 10"
39
39
  end
40
40
 
41
+ def quoting
42
+ # unknown
43
+ end
44
+
41
45
  protected
42
46
 
43
47
  def client
@@ -25,6 +25,15 @@ module Blazer
25
25
  "SELECT * FROM {table} LIMIT 10"
26
26
  end
27
27
 
28
+ def quoting
29
+ :single_quote_escape
30
+ end
31
+
32
+ # TODO support prepared statements - https://prestodb.io/docs/current/sql/prepare.html
33
+ # feature request for variables - https://github.com/prestodb/presto/issues/5918
34
+ def parameter_binding
35
+ end
36
+
28
37
  protected
29
38
 
30
39
  def client