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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +55 -9
- 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 +28 -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 +4 -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 +51 -15
- 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 +30 -3
- data/lib/blazer/data_source.rb +85 -5
- data/lib/blazer/engine.rb +0 -4
- 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
@@ -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:
|
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 "
|
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, 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
|
@@ -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
|
@@ -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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
@@ -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
|
@@ -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
|