blazer 2.4.2 → 2.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -0
- data/README.md +155 -57
- data/app/assets/javascripts/blazer/Chart.js +14000 -13979
- data/app/assets/javascripts/blazer/bootstrap.js +300 -97
- data/app/assets/javascripts/blazer/queries.js +12 -1
- data/app/assets/javascripts/blazer/vue.js +10754 -9687
- data/app/assets/stylesheets/blazer/application.css +5 -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} +527 -455
- 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 +31 -49
- data/app/models/blazer/query.rb +9 -3
- data/app/views/blazer/_variables.html.erb +5 -4
- data/app/views/blazer/dashboards/_form.html.erb +1 -1
- data/app/views/blazer/dashboards/show.html.erb +6 -4
- data/app/views/blazer/queries/_caching.html.erb +1 -1
- data/app/views/blazer/queries/_form.html.erb +3 -3
- data/app/views/blazer/queries/run.html.erb +5 -3
- 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 +73 -20
- data/lib/blazer/adapters/base_adapter.rb +16 -1
- data/lib/blazer/adapters/bigquery_adapter.rb +14 -3
- 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 +19 -4
- 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 +52 -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 +41 -4
- data/{app/mailers → lib}/blazer/check_mailer.rb +0 -0
- data/lib/blazer/data_source.rb +90 -8
- data/lib/blazer/engine.rb +1 -4
- data/lib/blazer/result.rb +19 -1
- data/lib/blazer/run_statement.rb +7 -3
- data/lib/blazer/run_statement_job.rb +4 -2
- data/{app/mailers → lib}/blazer/slack_notifier.rb +19 -4
- data/lib/blazer/statement.rb +75 -0
- data/lib/blazer/version.rb +1 -1
- data/lib/blazer.rb +32 -8
- data/lib/generators/blazer/templates/config.yml.tt +2 -2
- data/lib/tasks/blazer.rake +5 -5
- data/licenses/LICENSE-bootstrap.txt +1 -1
- metadata +10 -6
@@ -1,7 +1,7 @@
|
|
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 = []
|
@@ -9,18 +9,43 @@ module Blazer
|
|
9
9
|
error = nil
|
10
10
|
|
11
11
|
begin
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
if settings["output_location"]
|
41
|
+
query_options[:result_configuration] = {output_location: settings["output_location"]}
|
42
|
+
end
|
43
|
+
|
44
|
+
if settings["workgroup"]
|
45
|
+
query_options[:work_group] = settings["workgroup"]
|
46
|
+
end
|
47
|
+
|
48
|
+
resp = client.start_query_execution(**query_options)
|
24
49
|
query_execution_id = resp.query_execution_id
|
25
50
|
|
26
51
|
timeout = data_source.timeout || 300
|
@@ -60,21 +85,21 @@ module Blazer
|
|
60
85
|
column_types.each_with_index do |ct, i|
|
61
86
|
# TODO more column_types
|
62
87
|
case ct
|
63
|
-
when "timestamp"
|
88
|
+
when "timestamp", "timestamp with time zone"
|
64
89
|
rows.each do |row|
|
65
|
-
row[i]
|
90
|
+
row[i] &&= utc.parse(row[i])
|
66
91
|
end
|
67
92
|
when "date"
|
68
93
|
rows.each do |row|
|
69
|
-
row[i]
|
94
|
+
row[i] &&= Date.parse(row[i])
|
70
95
|
end
|
71
96
|
when "bigint"
|
72
97
|
rows.each do |row|
|
73
|
-
row[i]
|
98
|
+
row[i] &&= row[i].to_i
|
74
99
|
end
|
75
100
|
when "double"
|
76
101
|
rows.each do |row|
|
77
|
-
row[i]
|
102
|
+
row[i] &&= row[i].to_f
|
78
103
|
end
|
79
104
|
end
|
80
105
|
end
|
@@ -105,12 +130,29 @@ module Blazer
|
|
105
130
|
"SELECT * FROM {table} LIMIT 10"
|
106
131
|
end
|
107
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
|
+
|
108
143
|
private
|
109
144
|
|
110
145
|
def database
|
111
146
|
@database ||= settings["database"] || "default"
|
112
147
|
end
|
113
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
|
+
|
114
156
|
def fetch_error(query_execution_id)
|
115
157
|
client.get_query_execution(
|
116
158
|
query_execution_id: query_execution_id
|
@@ -118,11 +160,22 @@ module Blazer
|
|
118
160
|
end
|
119
161
|
|
120
162
|
def client
|
121
|
-
@client ||= Aws::Athena::Client.new
|
163
|
+
@client ||= Aws::Athena::Client.new(**client_options)
|
122
164
|
end
|
123
165
|
|
124
166
|
def glue
|
125
|
-
@glue ||= Aws::Glue::Client.new
|
167
|
+
@glue ||= Aws::Glue::Client.new(**client_options)
|
168
|
+
end
|
169
|
+
|
170
|
+
def client_options
|
171
|
+
@client_options ||= begin
|
172
|
+
options = {}
|
173
|
+
if settings["access_key_id"] || settings["secret_access_key"]
|
174
|
+
options[:credentials] = Aws::Credentials.new(settings["access_key_id"], settings["secret_access_key"])
|
175
|
+
end
|
176
|
+
options[:region] = settings["region"] if settings["region"]
|
177
|
+
options
|
178
|
+
end
|
126
179
|
end
|
127
180
|
end
|
128
181
|
end
|
@@ -8,7 +8,22 @@ module Blazer
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def run_statement(statement, comment)
|
11
|
-
#
|
11
|
+
# required
|
12
|
+
end
|
13
|
+
|
14
|
+
def quoting
|
15
|
+
# required, how to quote variables
|
16
|
+
# :backslash_escape - single quote strings and convert ' to \' and \ to \\
|
17
|
+
# :single_quote_escape - single quote strings and convert ' to ''
|
18
|
+
# ->(value) { ... } - custom method
|
19
|
+
end
|
20
|
+
|
21
|
+
def parameter_binding
|
22
|
+
# optional, but recommended when possible for security
|
23
|
+
# if specified, quoting is only used for display
|
24
|
+
# :positional - ?
|
25
|
+
# :numeric - $1
|
26
|
+
# ->(statement, values) { ... } - custom method
|
12
27
|
end
|
13
28
|
|
14
29
|
def tables
|
@@ -1,24 +1,25 @@
|
|
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
|
14
14
|
if !results.respond_to?(:complete?) || results.complete?
|
15
15
|
columns = results.first.keys.map(&:to_s) if results.size > 0
|
16
|
-
rows = results.map(&:values)
|
16
|
+
rows = results.all.map(&:values)
|
17
17
|
else
|
18
18
|
error = Blazer::TIMEOUT_MESSAGE
|
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,26 +1,27 @@
|
|
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.
|
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
|
-
date_indexes = response["columns"].each_index.select { |i| response["columns"][i]["type"]
|
13
|
+
date_indexes = response["columns"].each_index.select { |i| ["date", "datetime"].include?(response["columns"][i]["type"]) }
|
14
14
|
if columns.any?
|
15
15
|
rows = response["rows"]
|
16
16
|
date_indexes.each do |i|
|
17
17
|
rows.each do |row|
|
18
|
-
row[i]
|
18
|
+
row[i] &&= Time.parse(row[i])
|
19
19
|
end
|
20
20
|
end
|
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,8 +37,22 @@ 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
|
|
52
|
+
def endpoint
|
53
|
+
@endpoint ||= client.info["version"]["number"].to_i >= 7 ? "_sql" : "_xpack/sql"
|
54
|
+
end
|
55
|
+
|
41
56
|
def client
|
42
57
|
@client ||= Elasticsearch::Client.new(url: settings["url"])
|
43
58
|
end
|
@@ -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
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class OpensearchAdapter < BaseAdapter
|
4
|
+
def run_statement(statement, comment)
|
5
|
+
columns = []
|
6
|
+
rows = []
|
7
|
+
error = nil
|
8
|
+
|
9
|
+
begin
|
10
|
+
response = client.transport.perform_request("POST", "_plugins/_sql", {}, {query: "#{statement} /*#{comment}*/"}).body
|
11
|
+
columns = response["schema"].map { |v| v["name"] }
|
12
|
+
# TODO typecast more types
|
13
|
+
# https://github.com/opensearch-project/sql/blob/main/docs/user/general/datatypes.rst
|
14
|
+
date_indexes = response["schema"].each_index.select { |i| response["schema"][i]["type"] == "timestamp" }
|
15
|
+
if columns.any?
|
16
|
+
rows = response["datarows"]
|
17
|
+
utc = ActiveSupport::TimeZone["Etc/UTC"]
|
18
|
+
date_indexes.each do |i|
|
19
|
+
rows.each do |row|
|
20
|
+
row[i] &&= utc.parse(row[i])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
rescue => e
|
25
|
+
error = e.message
|
26
|
+
end
|
27
|
+
|
28
|
+
[columns, rows, error]
|
29
|
+
end
|
30
|
+
|
31
|
+
def tables
|
32
|
+
indices = client.cat.indices(format: "json").map { |v| v["index"] }
|
33
|
+
aliases = client.cat.aliases(format: "json").map { |v| v["alias"] }
|
34
|
+
(indices + aliases).uniq.sort
|
35
|
+
end
|
36
|
+
|
37
|
+
def preview_statement
|
38
|
+
"SELECT * FROM `{table}` LIMIT 10"
|
39
|
+
end
|
40
|
+
|
41
|
+
def quoting
|
42
|
+
# unknown
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def client
|
48
|
+
@client ||= OpenSearch::Client.new(url: settings["url"])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -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
|