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.
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
@@ -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
@@ -35,6 +35,11 @@ module Blazer
35
35
  "SELECT Id FROM {table} LIMIT 10"
36
36
  end
37
37
 
38
+ # https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_quotedstringescapes.htm
39
+ def quoting
40
+ :backslash_escape
41
+ end
42
+
38
43
  protected
39
44
 
40
45
  def client
@@ -68,6 +68,15 @@ module Blazer
68
68
  def cancel(run_id)
69
69
  # todo
70
70
  end
71
+
72
+ # https://docs.snowflake.com/en/sql-reference/data-types-text.html#escape-sequences
73
+ def quoting
74
+ :backslash_escape
75
+ end
76
+
77
+ def parameter_binding
78
+ # TODO
79
+ end
71
80
  end
72
81
  end
73
82
  end
@@ -2,6 +2,10 @@ module Blazer
2
2
  module Adapters
3
3
  class SodaAdapter < BaseAdapter
4
4
  def run_statement(statement, comment)
5
+ require "json"
6
+ require "net/http"
7
+ require "uri"
8
+
5
9
  columns = []
6
10
  rows = []
7
11
  error = nil
@@ -91,6 +95,11 @@ module Blazer
91
95
  def tables
92
96
  ["all"]
93
97
  end
98
+
99
+ # https://dev.socrata.com/docs/datatypes/text.html
100
+ def quoting
101
+ :single_quote_escape
102
+ end
94
103
  end
95
104
  end
96
105
  end
@@ -4,6 +4,11 @@ module Blazer
4
4
  def tables
5
5
  client.execute("SHOW TABLES").map { |r| r["tableName"] }
6
6
  end
7
+
8
+ # https://spark.apache.org/docs/latest/sql-ref-literals.html
9
+ def quoting
10
+ :backslash_escape
11
+ end
7
12
  end
8
13
  end
9
14
  end
@@ -15,7 +15,7 @@ module Blazer
15
15
  end
16
16
  end
17
17
 
18
- def run_statement(statement, comment)
18
+ def run_statement(statement, comment, bind_params = [])
19
19
  columns = []
20
20
  rows = []
21
21
  error = nil
@@ -24,7 +24,8 @@ module Blazer
24
24
  in_transaction do
25
25
  set_timeout(data_source.timeout) if data_source.timeout
26
26
 
27
- result = select_all("#{statement} /*#{comment}*/")
27
+ binds = bind_params.map { |v| ActiveRecord::Relation::QueryAttribute.new(nil, v, ActiveRecord::Type::Value.new) }
28
+ result = connection_model.connection.select_all("#{statement} /*#{comment}*/", nil, binds)
28
29
  columns = result.columns
29
30
  result.rows.each do |untyped_row|
30
31
  rows << (result.column_types.empty? ? untyped_row : columns.each_with_index.map { |c, i| untyped_row[i] && result.column_types[c] ? result.column_types[c].send(:cast_value, untyped_row[i]) : untyped_row[i] })
@@ -33,6 +34,7 @@ module Blazer
33
34
  rescue => e
34
35
  error = e.message.sub(/.+ERROR: /, "")
35
36
  error = Blazer::TIMEOUT_MESSAGE if Blazer::TIMEOUT_ERRORS.any? { |e| error.include?(e) }
37
+ error = Blazer::VARIABLE_MESSAGE if error.include?("syntax error at or near \"$") || error.include?("Incorrect syntax near '@") || error.include?("your MySQL server version for the right syntax to use near '?")
36
38
  reconnect if error.include?("PG::ConnectionBad")
37
39
  end
38
40
 
@@ -156,7 +158,7 @@ module Blazer
156
158
  # WITH not an optimization fence in Postgres 12+
157
159
  statement = <<~SQL
158
160
  WITH query AS (
159
- #{statement}
161
+ {placeholder}
160
162
  ),
161
163
  cohorts AS (
162
164
  SELECT user_id, MIN(#{cohort_column}) AS cohort_time FROM query
@@ -183,6 +185,30 @@ module Blazer
183
185
  connection_model.send(:sanitize_sql_array, params)
184
186
  end
185
187
 
188
+ def quoting
189
+ ->(value) { connection_model.connection.quote(value) }
190
+ end
191
+
192
+ # Redshift adapter silently ignores binds
193
+ def parameter_binding
194
+ if postgresql? && (ActiveRecord::VERSION::STRING.to_f >= 6.1 || prepared_statements?)
195
+ # Active Record < 6.1 silently ignores binds with Postgres when prepared statements are disabled
196
+ :numeric
197
+ elsif sqlite?
198
+ :numeric
199
+ elsif mysql? && prepared_statements?
200
+ # Active Record silently ignores binds with MySQL when prepared statements are disabled
201
+ :positional
202
+ elsif sqlserver?
203
+ proc do |statement, variables|
204
+ variables.each_with_index do |(k, _), i|
205
+ statement = statement.gsub("{#{k}}", "@#{i} ")
206
+ end
207
+ [statement, variables.values]
208
+ end
209
+ end
210
+ end
211
+
186
212
  protected
187
213
 
188
214
  def select_all(statement, params = [])
@@ -207,6 +233,10 @@ module Blazer
207
233
  ["MySQL", "Mysql2", "Mysql2Spatial"].include?(adapter_name)
208
234
  end
209
235
 
236
+ def sqlite?
237
+ ["SQLite"].include?(adapter_name)
238
+ end
239
+
210
240
  def sqlserver?
211
241
  ["SQLServer", "tinytds", "mssql"].include?(adapter_name)
212
242
  end
@@ -282,6 +312,10 @@ module Blazer
282
312
  end
283
313
  end
284
314
  end
315
+
316
+ def prepared_statements?
317
+ connection_model.connection.prepared_statements
318
+ end
285
319
  end
286
320
  end
287
321
  end
@@ -89,7 +89,19 @@ module Blazer
89
89
  Blazer.cache.delete(run_cache_key(run_id))
90
90
  end
91
91
 
92
+ def sub_variables(statement, vars)
93
+ statement = statement.dup
94
+ vars.each do |var, value|
95
+ # use block form to disable back-references
96
+ statement.gsub!("{#{var}}") { quote(value) }
97
+ end
98
+ statement
99
+ end
100
+
92
101
  def run_statement(statement, options = {})
102
+ statement = Statement.new(statement, self) if statement.is_a?(String)
103
+ statement.bind unless statement.bind_statement
104
+
93
105
  async = options[:async]
94
106
  result = nil
95
107
  if cache_mode != "off"
@@ -118,7 +130,7 @@ module Blazer
118
130
  if options[:run_id]
119
131
  comment << ",run_id:#{options[:run_id]}"
120
132
  end
121
- result = run_statement_helper(statement, comment, async ? options[:run_id] : nil)
133
+ result = run_statement_helper(statement, comment, async ? options[:run_id] : nil, options)
122
134
  end
123
135
 
124
136
  result
@@ -133,13 +145,68 @@ module Blazer
133
145
  end
134
146
 
135
147
  def statement_cache_key(statement)
136
- cache_key(["statement", id, Digest::MD5.hexdigest(statement.to_s.gsub("\r\n", "\n"))])
148
+ cache_key(["statement", id, Digest::MD5.hexdigest(statement.bind_statement.to_s.gsub("\r\n", "\n") + statement.bind_values.to_json)])
137
149
  end
138
150
 
139
151
  def run_cache_key(run_id)
140
152
  cache_key(["run", run_id])
141
153
  end
142
154
 
155
+ def quote(value)
156
+ if quoting == :backslash_escape || quoting == :single_quote_escape
157
+ # only need to support types generated by process_vars
158
+ if value.is_a?(Integer) || value.is_a?(Float)
159
+ value.to_s
160
+ elsif value.nil?
161
+ "NULL"
162
+ else
163
+ value = value.to_formatted_s(:db) if value.is_a?(ActiveSupport::TimeWithZone)
164
+
165
+ if quoting == :backslash_escape
166
+ "'#{value.gsub("\\") { "\\\\" }.gsub("'") { "\\'" }}'"
167
+ else
168
+ "'#{value.gsub("'", "''")}'"
169
+ end
170
+ end
171
+ elsif quoting.respond_to?(:call)
172
+ quoting.call(value)
173
+ elsif quoting.nil?
174
+ raise Blazer::Error, "Quoting not specified"
175
+ else
176
+ raise Blazer::Error, "Unknown quoting"
177
+ end
178
+ end
179
+
180
+ def bind_params(statement, variables)
181
+ if parameter_binding == :positional
182
+ locations = []
183
+ variables.each do |k, v|
184
+ i = 0
185
+ while (idx = statement.index("{#{k}}", i))
186
+ locations << [v, idx]
187
+ i = idx + 1
188
+ end
189
+ end
190
+ variables.each do |k, v|
191
+ statement = statement.gsub("{#{k}}", "?")
192
+ end
193
+ [statement, locations.sort_by(&:last).map(&:first)]
194
+ elsif parameter_binding == :numeric
195
+ variables.each_with_index do |(k, v), i|
196
+ # add trailing space if followed by digit
197
+ # try to keep minimal to avoid fixing invalid queries like SELECT{var}
198
+ statement = statement.gsub(/#{Regexp.escape("{#{k}}")}(\d)/, "$#{i + 1} \\1").gsub("{#{k}}", "$#{i + 1}")
199
+ end
200
+ [statement, variables.values]
201
+ elsif parameter_binding.respond_to?(:call)
202
+ parameter_binding.call(statement, variables)
203
+ elsif parameter_binding.nil?
204
+ [sub_variables(statement, variables), []]
205
+ else
206
+ raise Blazer::Error, "Unknown bind parameters"
207
+ end
208
+ end
209
+
143
210
  protected
144
211
 
145
212
  def adapter_instance
@@ -157,9 +224,22 @@ module Blazer
157
224
  end
158
225
  end
159
226
 
160
- def run_statement_helper(statement, comment, run_id)
227
+ def quoting
228
+ @quoting ||= adapter_instance.quoting
229
+ end
230
+
231
+ def parameter_binding
232
+ @parameter_binding ||= adapter_instance.parameter_binding
233
+ end
234
+
235
+ def run_statement_helper(statement, comment, run_id, options)
161
236
  start_time = Time.now
162
- columns, rows, error = adapter_instance.run_statement(statement, comment)
237
+ columns, rows, error =
238
+ if adapter_instance.parameter_binding
239
+ adapter_instance.run_statement(statement.bind_statement, comment, statement.bind_values)
240
+ else
241
+ adapter_instance.run_statement(statement.bind_statement, comment)
242
+ end
163
243
  duration = Time.now - start_time
164
244
 
165
245
  cache_data = nil
@@ -168,7 +248,7 @@ module Blazer
168
248
  cache_data = Marshal.dump([columns, rows, error, cache ? Time.now : nil]) rescue nil
169
249
  end
170
250
 
171
- if cache && cache_data && adapter_instance.cachable?(statement)
251
+ if cache && cache_data && adapter_instance.cachable?(statement.bind_statement)
172
252
  Blazer.cache.write(statement_cache_key(statement), cache_data, expires_in: cache_expires_in.to_f * 60)
173
253
  end
174
254
 
data/lib/blazer/engine.rb CHANGED
@@ -30,10 +30,6 @@ module Blazer
30
30
  Blazer.anomaly_checks = Blazer.settings["anomaly_checks"] || false
31
31
  Blazer.forecasting = Blazer.settings["forecasting"] || false
32
32
  Blazer.async = Blazer.settings["async"] || false
33
- if Blazer.async
34
- require "blazer/run_statement_job"
35
- end
36
-
37
33
  Blazer.images = Blazer.settings["images"] || false
38
34
  Blazer.override_csp = Blazer.settings["override_csp"] || false
39
35
  Blazer.slack_oauth_token = Blazer.settings["slack_oauth_token"] || ENV["BLAZER_SLACK_OAUTH_TOKEN"]
data/lib/blazer/result.rb CHANGED
@@ -56,6 +56,8 @@ module Blazer
56
56
  "time"
57
57
  elsif v.nil?
58
58
  nil
59
+ elsif v.is_a?(String) && v.encoding == Encoding::BINARY
60
+ "binary"
59
61
  else
60
62
  "string"
61
63
  end
@@ -1,12 +1,16 @@
1
1
  module Blazer
2
2
  class RunStatement
3
- def perform(data_source, statement, options = {})
3
+ def perform(statement, options = {})
4
4
  query = options[:query]
5
- Blazer.transform_statement.call(data_source, statement) if Blazer.transform_statement
5
+
6
+ data_source = statement.data_source
7
+ statement.bind
6
8
 
7
9
  # audit
8
10
  if Blazer.audit
9
- audit = Blazer::Audit.new(statement: statement)
11
+ audit_statement = statement.bind_statement
12
+ audit_statement += "\n\n#{statement.bind_values.to_json}" if statement.bind_values.any?
13
+ audit = Blazer::Audit.new(statement: audit_statement)
10
14
  audit.query = query
11
15
  audit.data_source = data_source.id
12
16
  audit.user = options[:user]
@@ -3,10 +3,12 @@ module Blazer
3
3
  self.queue_adapter = :async
4
4
 
5
5
  def perform(data_source_id, statement, options)
6
- data_source = Blazer.data_sources[data_source_id]
6
+ statement = Blazer::Statement.new(statement, data_source_id)
7
+ statement.values = options.delete(:values)
8
+ data_source = statement.data_source
7
9
  begin
8
10
  ActiveRecord::Base.connection_pool.with_connection do
9
- Blazer::RunStatement.new.perform(data_source, statement, options)
11
+ Blazer::RunStatement.new.perform(statement, options)
10
12
  end
11
13
  rescue Exception => e
12
14
  Blazer::Result.new(data_source, [], [], "Unknown error", nil, false)
@@ -67,15 +67,18 @@ module Blazer
67
67
  Blazer::Engine.routes.url_helpers.query_url(id, ActionMailer::Base.default_url_options)
68
68
  end
69
69
 
70
+ # TODO use return value
70
71
  def self.post(payload)
71
72
  if Blazer.slack_webhook_url.present?
72
- post_api(Blazer.slack_webhook_url, payload, {})
73
+ response = post_api(Blazer.slack_webhook_url, payload, {})
74
+ response.is_a?(Net::HTTPSuccess) && response.body == "ok"
73
75
  else
74
76
  headers = {
75
77
  "Authorization" => "Bearer #{Blazer.slack_oauth_token}",
76
78
  "Content-type" => "application/json"
77
79
  }
78
- post_api("https://slack.com/api/chat.postMessage", payload, headers)
80
+ response = post_api("https://slack.com/api/chat.postMessage", payload, headers)
81
+ response.is_a?(Net::HTTPSuccess) && (JSON.parse(response.body)["ok"] rescue false)
79
82
  end
80
83
  end
81
84