finery 3.0.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 (153) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +426 -0
  3. data/CONTRIBUTING.md +49 -0
  4. data/LICENSE.txt +25 -0
  5. data/README.md +1144 -0
  6. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  7. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  11. data/app/assets/images/blazer/favicon.png +0 -0
  12. data/app/assets/javascripts/blazer/Sortable.js +3709 -0
  13. data/app/assets/javascripts/blazer/ace/ace.js +19630 -0
  14. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1981 -0
  15. data/app/assets/javascripts/blazer/ace/mode-sql.js +215 -0
  16. data/app/assets/javascripts/blazer/ace/snippets/sql.js +16 -0
  17. data/app/assets/javascripts/blazer/ace/snippets/text.js +9 -0
  18. data/app/assets/javascripts/blazer/ace/theme-twilight.js +18 -0
  19. data/app/assets/javascripts/blazer/ace.js +6 -0
  20. data/app/assets/javascripts/blazer/application.js +87 -0
  21. data/app/assets/javascripts/blazer/bootstrap.js +2580 -0
  22. data/app/assets/javascripts/blazer/chart.umd.js +13 -0
  23. data/app/assets/javascripts/blazer/chartjs-adapter-date-fns.bundle.js +6322 -0
  24. data/app/assets/javascripts/blazer/chartjs-plugin-annotation.min.js +7 -0
  25. data/app/assets/javascripts/blazer/chartkick.js +2570 -0
  26. data/app/assets/javascripts/blazer/daterangepicker.js +1578 -0
  27. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  28. data/app/assets/javascripts/blazer/highlight.min.js +466 -0
  29. data/app/assets/javascripts/blazer/jquery.js +10872 -0
  30. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +325 -0
  31. data/app/assets/javascripts/blazer/mapkick.bundle.js +1029 -0
  32. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1548 -0
  33. data/app/assets/javascripts/blazer/moment.js +5685 -0
  34. data/app/assets/javascripts/blazer/queries.js +130 -0
  35. data/app/assets/javascripts/blazer/rails-ujs.js +746 -0
  36. data/app/assets/javascripts/blazer/routes.js +26 -0
  37. data/app/assets/javascripts/blazer/selectize.js +3891 -0
  38. data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
  39. data/app/assets/javascripts/blazer/stupidtable.js +281 -0
  40. data/app/assets/javascripts/blazer/vue.global.prod.js +1 -0
  41. data/app/assets/stylesheets/blazer/application.css +243 -0
  42. data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
  43. data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
  44. data/app/assets/stylesheets/blazer/bootstrap.css +6828 -0
  45. data/app/assets/stylesheets/blazer/daterangepicker.css +410 -0
  46. data/app/assets/stylesheets/blazer/github.css +125 -0
  47. data/app/assets/stylesheets/blazer/selectize.css +403 -0
  48. data/app/controllers/blazer/base_controller.rb +133 -0
  49. data/app/controllers/blazer/checks_controller.rb +56 -0
  50. data/app/controllers/blazer/dashboards_controller.rb +99 -0
  51. data/app/controllers/blazer/queries_controller.rb +468 -0
  52. data/app/controllers/blazer/uploads_controller.rb +147 -0
  53. data/app/helpers/blazer/base_helper.rb +83 -0
  54. data/app/models/blazer/audit.rb +6 -0
  55. data/app/models/blazer/check.rb +104 -0
  56. data/app/models/blazer/connection.rb +5 -0
  57. data/app/models/blazer/dashboard.rb +17 -0
  58. data/app/models/blazer/dashboard_query.rb +9 -0
  59. data/app/models/blazer/query.rb +42 -0
  60. data/app/models/blazer/record.rb +5 -0
  61. data/app/models/blazer/upload.rb +11 -0
  62. data/app/models/blazer/uploads_connection.rb +7 -0
  63. data/app/views/blazer/_nav.html.erb +18 -0
  64. data/app/views/blazer/_variables.html.erb +127 -0
  65. data/app/views/blazer/check_mailer/failing_checks.html.erb +7 -0
  66. data/app/views/blazer/check_mailer/state_change.html.erb +48 -0
  67. data/app/views/blazer/checks/_form.html.erb +79 -0
  68. data/app/views/blazer/checks/edit.html.erb +3 -0
  69. data/app/views/blazer/checks/index.html.erb +72 -0
  70. data/app/views/blazer/checks/new.html.erb +3 -0
  71. data/app/views/blazer/dashboards/_form.html.erb +82 -0
  72. data/app/views/blazer/dashboards/edit.html.erb +3 -0
  73. data/app/views/blazer/dashboards/new.html.erb +3 -0
  74. data/app/views/blazer/dashboards/show.html.erb +53 -0
  75. data/app/views/blazer/queries/_caching.html.erb +16 -0
  76. data/app/views/blazer/queries/_cohorts.html.erb +48 -0
  77. data/app/views/blazer/queries/_form.html.erb +255 -0
  78. data/app/views/blazer/queries/docs.html.erb +147 -0
  79. data/app/views/blazer/queries/edit.html.erb +2 -0
  80. data/app/views/blazer/queries/home.html.erb +169 -0
  81. data/app/views/blazer/queries/new.html.erb +2 -0
  82. data/app/views/blazer/queries/run.html.erb +188 -0
  83. data/app/views/blazer/queries/schema.html.erb +55 -0
  84. data/app/views/blazer/queries/show.html.erb +72 -0
  85. data/app/views/blazer/uploads/_form.html.erb +27 -0
  86. data/app/views/blazer/uploads/edit.html.erb +3 -0
  87. data/app/views/blazer/uploads/index.html.erb +55 -0
  88. data/app/views/blazer/uploads/new.html.erb +3 -0
  89. data/app/views/layouts/blazer/application.html.erb +25 -0
  90. data/config/routes.rb +25 -0
  91. data/lib/blazer/adapters/athena_adapter.rb +182 -0
  92. data/lib/blazer/adapters/base_adapter.rb +76 -0
  93. data/lib/blazer/adapters/bigquery_adapter.rb +79 -0
  94. data/lib/blazer/adapters/cassandra_adapter.rb +70 -0
  95. data/lib/blazer/adapters/clickhouse_adapter.rb +84 -0
  96. data/lib/blazer/adapters/drill_adapter.rb +38 -0
  97. data/lib/blazer/adapters/druid_adapter.rb +102 -0
  98. data/lib/blazer/adapters/elasticsearch_adapter.rb +61 -0
  99. data/lib/blazer/adapters/hive_adapter.rb +55 -0
  100. data/lib/blazer/adapters/ignite_adapter.rb +64 -0
  101. data/lib/blazer/adapters/influxdb_adapter.rb +57 -0
  102. data/lib/blazer/adapters/neo4j_adapter.rb +62 -0
  103. data/lib/blazer/adapters/opensearch_adapter.rb +52 -0
  104. data/lib/blazer/adapters/presto_adapter.rb +54 -0
  105. data/lib/blazer/adapters/salesforce_adapter.rb +50 -0
  106. data/lib/blazer/adapters/snowflake_adapter.rb +82 -0
  107. data/lib/blazer/adapters/soda_adapter.rb +105 -0
  108. data/lib/blazer/adapters/spark_adapter.rb +14 -0
  109. data/lib/blazer/adapters/sql_adapter.rb +324 -0
  110. data/lib/blazer/adapters.rb +18 -0
  111. data/lib/blazer/annotations.rb +47 -0
  112. data/lib/blazer/anomaly_detectors.rb +22 -0
  113. data/lib/blazer/check_mailer.rb +27 -0
  114. data/lib/blazer/data_source.rb +270 -0
  115. data/lib/blazer/engine.rb +42 -0
  116. data/lib/blazer/forecasters.rb +7 -0
  117. data/lib/blazer/result.rb +178 -0
  118. data/lib/blazer/result_cache.rb +71 -0
  119. data/lib/blazer/run_statement.rb +44 -0
  120. data/lib/blazer/run_statement_job.rb +20 -0
  121. data/lib/blazer/slack_notifier.rb +94 -0
  122. data/lib/blazer/statement.rb +77 -0
  123. data/lib/blazer/version.rb +3 -0
  124. data/lib/blazer.rb +286 -0
  125. data/lib/finery.rb +3 -0
  126. data/lib/generators/blazer/install_generator.rb +22 -0
  127. data/lib/generators/blazer/templates/config.yml.tt +83 -0
  128. data/lib/generators/blazer/templates/install.rb.tt +47 -0
  129. data/lib/generators/blazer/templates/uploads.rb.tt +10 -0
  130. data/lib/generators/blazer/uploads_generator.rb +18 -0
  131. data/lib/tasks/blazer.rake +20 -0
  132. data/lib/tasks/finery.rake +20 -0
  133. data/licenses/LICENSE-ace.txt +24 -0
  134. data/licenses/LICENSE-bootstrap.txt +21 -0
  135. data/licenses/LICENSE-chart.js.txt +9 -0
  136. data/licenses/LICENSE-chartjs-adapter-date-fns.txt +9 -0
  137. data/licenses/LICENSE-chartkick.js.txt +22 -0
  138. data/licenses/LICENSE-date-fns.txt +21 -0
  139. data/licenses/LICENSE-daterangepicker.txt +21 -0
  140. data/licenses/LICENSE-fuzzysearch.txt +20 -0
  141. data/licenses/LICENSE-highlight.js.txt +29 -0
  142. data/licenses/LICENSE-jquery.txt +20 -0
  143. data/licenses/LICENSE-kurkle-color.txt +9 -0
  144. data/licenses/LICENSE-mapkick-bundle.txt +1029 -0
  145. data/licenses/LICENSE-moment-timezone.txt +20 -0
  146. data/licenses/LICENSE-moment.txt +22 -0
  147. data/licenses/LICENSE-rails-ujs.txt +20 -0
  148. data/licenses/LICENSE-selectize.txt +202 -0
  149. data/licenses/LICENSE-sortable.txt +21 -0
  150. data/licenses/LICENSE-stickytableheaders.txt +20 -0
  151. data/licenses/LICENSE-stupidtable.txt +19 -0
  152. data/licenses/LICENSE-vue.txt +21 -0
  153. metadata +250 -0
@@ -0,0 +1,76 @@
1
+ module Blazer
2
+ module Adapters
3
+ class BaseAdapter
4
+ attr_reader :data_source
5
+
6
+ def initialize(data_source)
7
+ @data_source = data_source
8
+ end
9
+
10
+ def run_statement(statement, comment)
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
27
+ end
28
+
29
+ def tables
30
+ [] # optional, but nice to have
31
+ end
32
+
33
+ def schema
34
+ [] # optional, but nice to have
35
+ end
36
+
37
+ def preview_statement
38
+ "" # also optional, but nice to have
39
+ end
40
+
41
+ def reconnect
42
+ # optional
43
+ end
44
+
45
+ def cost(statement)
46
+ # optional
47
+ end
48
+
49
+ def explain(statement)
50
+ # optional
51
+ end
52
+
53
+ def cancel(run_id)
54
+ # optional
55
+ end
56
+
57
+ def cachable?(statement)
58
+ true # optional
59
+ end
60
+
61
+ def supports_cohort_analysis?
62
+ false # optional
63
+ end
64
+
65
+ def cohort_analysis_statement(statement, period:, days:)
66
+ # optional
67
+ end
68
+
69
+ protected
70
+
71
+ def settings
72
+ @data_source.settings
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,79 @@
1
+ module Blazer
2
+ module Adapters
3
+ class BigQueryAdapter < BaseAdapter
4
+ def run_statement(statement, comment, bind_params)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ results = bigquery.query(statement, params: bind_params)
11
+
12
+ # complete? was removed in google-cloud-bigquery 0.29.0
13
+ # code is for backward compatibility
14
+ if !results.respond_to?(:complete?) || results.complete?
15
+ columns = results.first.keys.map(&:to_s) if results.size > 0
16
+ rows = results.all.map(&:values)
17
+ else
18
+ error = Blazer::TIMEOUT_MESSAGE
19
+ end
20
+ rescue => e
21
+ error = e.message
22
+ error = Blazer::VARIABLE_MESSAGE if error.include?("Syntax error: Unexpected \"?\"")
23
+ end
24
+
25
+ [columns, rows, error]
26
+ end
27
+
28
+ def tables
29
+ table_refs.map { |t| "#{t.project_id}.#{t.dataset_id}.#{t.table_id}" }
30
+ end
31
+
32
+ def schema
33
+ table_refs.map do |table_ref|
34
+ {
35
+ schema: table_ref.dataset_id,
36
+ table: table_ref.table_id,
37
+ columns: table_columns(table_ref)
38
+ }
39
+ end
40
+ end
41
+
42
+ def preview_statement
43
+ "SELECT * FROM `{table}` LIMIT 10"
44
+ end
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
+
56
+ private
57
+
58
+ def bigquery
59
+ @bigquery ||= begin
60
+ require "google/cloud/bigquery"
61
+ Google::Cloud::Bigquery.new(
62
+ project: settings["project"],
63
+ keyfile: settings["keyfile"]
64
+ )
65
+ end
66
+ end
67
+
68
+ def table_refs
69
+ bigquery.datasets.map(&:tables).flat_map { |table_list| table_list.map(&:table_ref) }
70
+ end
71
+
72
+ def table_columns(table_ref)
73
+ schema = bigquery.service.get_table(table_ref.dataset_id, table_ref.table_id).schema
74
+ return [] if schema.nil?
75
+ schema.fields.map { |field| {name: field.name, data_type: field.type} }
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,70 @@
1
+ module Blazer
2
+ module Adapters
3
+ class CassandraAdapter < BaseAdapter
4
+ def run_statement(statement, comment, bind_params)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ response = session.execute("#{statement} /*#{comment}*/", arguments: bind_params)
11
+ rows = response.map { |r| r.values }
12
+ columns = rows.any? ? response.first.keys : []
13
+ rescue => e
14
+ error = e.message
15
+ error = Blazer::VARIABLE_MESSAGE if error.include?("no viable alternative at input '?'")
16
+ end
17
+
18
+ [columns, rows, error]
19
+ end
20
+
21
+ def tables
22
+ session.execute("SELECT table_name FROM system_schema.tables WHERE keyspace_name = #{data_source.quote(keyspace)}").map { |r| r["table_name"] }
23
+ end
24
+
25
+ def schema
26
+ result = session.execute("SELECT keyspace_name, table_name, column_name, type, position FROM system_schema.columns WHERE keyspace_name = #{data_source.quote(keyspace)}")
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]} }} }
28
+ end
29
+
30
+ def preview_statement
31
+ "SELECT * FROM {table} LIMIT 10"
32
+ end
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
+
44
+ private
45
+
46
+ def cluster
47
+ @cluster ||= begin
48
+ require "cassandra"
49
+ options = {hosts: [uri.host]}
50
+ options[:port] = uri.port if uri.port
51
+ options[:username] = uri.user if uri.user
52
+ options[:password] = uri.password if uri.password
53
+ ::Cassandra.cluster(options)
54
+ end
55
+ end
56
+
57
+ def session
58
+ @session ||= cluster.connect(keyspace)
59
+ end
60
+
61
+ def uri
62
+ @uri ||= URI.parse(data_source.settings["url"])
63
+ end
64
+
65
+ def keyspace
66
+ @keyspace ||= uri.path[1..-1]
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,84 @@
1
+ module Blazer
2
+ module Adapters
3
+ class ClickhouseAdapter < BaseAdapter
4
+ DATE_TIME_TYPES = ["DateTime", "DateTime(%s)", "DateTime64(%d, %s)"].freeze
5
+
6
+ def run_statement(statement, _comment)
7
+ columns = []
8
+ rows = []
9
+ error = nil
10
+
11
+ begin
12
+ result = connection.select_all(statement)
13
+ unless result.data.blank?
14
+ date_time_columns = result.meta
15
+ .select { |column| column["type"].in?(DATE_TIME_TYPES) }
16
+ .map { |column| column["name"] }
17
+ columns = result.first.keys
18
+ rows = result.map { |row| convert_time_columns(row, date_time_columns).values }
19
+ end
20
+ rescue => e
21
+ error = e.message
22
+ end
23
+
24
+ [columns, rows, error]
25
+ end
26
+
27
+ def tables
28
+ connection.tables
29
+ end
30
+
31
+ def schema
32
+ statement = <<-SQL
33
+ SELECT table, name, type
34
+ FROM system.columns
35
+ WHERE database = currentDatabase()
36
+ ORDER BY table, position
37
+ SQL
38
+
39
+ response = connection.post(query: { query: statement, default_format: "CSV" })
40
+ response.body
41
+ .group_by { |row| row[0] }
42
+ .transform_values { |columns| columns.map { |c| { name: c[1], data_type: c[2] } } }
43
+ .map { |table, columns| { schema: "public", table: table, columns: columns } }
44
+ end
45
+
46
+ def preview_statement
47
+ "SELECT * FROM {table} LIMIT 10"
48
+ end
49
+
50
+ def explain(statement)
51
+ connection.explain(statement)
52
+ end
53
+
54
+ protected
55
+
56
+ def connection
57
+ @connection ||= ClickHouse::Connection.new(config)
58
+ end
59
+
60
+ def config
61
+ @config ||= begin
62
+ uri = URI.parse(settings["url"])
63
+ options = {
64
+ scheme: uri.scheme,
65
+ host: uri.host,
66
+ port: uri.port,
67
+ username: uri.user,
68
+ password: uri.password,
69
+ database: uri.path.sub(/\A\//, ""),
70
+ ssl_verify: settings.fetch("ssl_verify", false)
71
+ }.compact
72
+ ClickHouse::Config.new(**options)
73
+ end
74
+ end
75
+
76
+ def convert_time_columns(row, date_time_columns)
77
+ time_values = row.slice(*date_time_columns).transform_values!(&:to_time)
78
+ row.merge(time_values)
79
+ rescue NoMethodError
80
+ row
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,38 @@
1
+ module Blazer
2
+ module Adapters
3
+ class DrillAdapter < BaseAdapter
4
+ def run_statement(statement, comment)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ # remove trailing semicolon
11
+ response = drill.query(statement.sub(/;\s*\z/, ""))
12
+ rows = response.map { |r| r.values }
13
+ columns = rows.any? ? response.first.keys : []
14
+ rescue => e
15
+ error = e.message
16
+ end
17
+
18
+ [columns, rows, error]
19
+ end
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
+
31
+ private
32
+
33
+ def drill
34
+ @drill ||= ::Drill.new(url: settings["url"])
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,102 @@
1
+ module Blazer
2
+ module Adapters
3
+ class DruidAdapter < BaseAdapter
4
+ TIMESTAMP_REGEX = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\z/
5
+
6
+ def run_statement(statement, comment, bind_params)
7
+ require "json"
8
+ require "net/http"
9
+ require "uri"
10
+
11
+ columns = []
12
+ rows = []
13
+ error = nil
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
+
32
+ header = {"Content-Type" => "application/json", "Accept" => "application/json"}
33
+ timeout = data_source.timeout ? data_source.timeout.to_i : 300
34
+ data = {
35
+ query: statement,
36
+ parameters: params,
37
+ context: {
38
+ timeout: timeout * 1000
39
+ }
40
+ }
41
+
42
+ uri = URI.parse("#{settings["url"]}/druid/v2/sql/")
43
+ http = Net::HTTP.new(uri.host, uri.port)
44
+ http.read_timeout = timeout
45
+
46
+ begin
47
+ response = JSON.parse(http.post(uri.request_uri, data.to_json, header).body)
48
+ if response.is_a?(Hash)
49
+ error = response["errorMessage"] || "Unknown error: #{response.inspect}"
50
+ if error.include?("timed out")
51
+ error = Blazer::TIMEOUT_MESSAGE
52
+ elsif error.include?("Encountered \"?\" at")
53
+ error = Blazer::VARIABLE_MESSAGE
54
+ end
55
+ else
56
+ columns = (response.first || {}).keys
57
+ rows = response.map { |r| r.values }
58
+
59
+ # Druid doesn't return column types
60
+ # and no timestamp type in JSON
61
+ rows.each do |row|
62
+ row.each_with_index do |v, i|
63
+ if v.is_a?(String) && TIMESTAMP_REGEX.match(v)
64
+ row[i] = Time.parse(v)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ rescue => e
70
+ error = e.message
71
+ end
72
+
73
+ [columns, rows, error]
74
+ end
75
+
76
+ def tables
77
+ result = data_source.run_statement("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA NOT IN ('INFORMATION_SCHEMA') ORDER BY TABLE_NAME")
78
+ result.rows.map(&:first)
79
+ end
80
+
81
+ def schema
82
+ result = data_source.run_statement("SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, ORDINAL_POSITION FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA NOT IN ('INFORMATION_SCHEMA') ORDER BY 1, 2")
83
+ 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]} }} }
84
+ end
85
+
86
+ def preview_statement
87
+ "SELECT * FROM {table} LIMIT 10"
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
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,61 @@
1
+ module Blazer
2
+ module Adapters
3
+ class ElasticsearchAdapter < BaseAdapter
4
+ def run_statement(statement, comment, bind_params)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ response = client.transport.perform_request("POST", endpoint, {}, {query: "#{statement} /*#{comment}*/", params: bind_params}).body
11
+ columns = response["columns"].map { |v| v["name"] }
12
+ # Elasticsearch does not differentiate between dates and times
13
+ date_indexes = response["columns"].each_index.select { |i| ["date", "datetime"].include?(response["columns"][i]["type"]) }
14
+ if columns.any?
15
+ rows = response["rows"]
16
+ date_indexes.each do |i|
17
+ rows.each do |row|
18
+ row[i] &&= Time.parse(row[i])
19
+ end
20
+ end
21
+ end
22
+ rescue => e
23
+ error = e.message
24
+ error = Blazer::VARIABLE_MESSAGE if error.include?("mismatched input '?'")
25
+ end
26
+
27
+ [columns, rows, error]
28
+ end
29
+
30
+ def tables
31
+ indices = client.cat.indices(format: "json").map { |v| v["index"] }
32
+ aliases = client.cat.aliases(format: "json").map { |v| v["alias"] }
33
+ (indices + aliases).uniq.sort
34
+ end
35
+
36
+ def preview_statement
37
+ "SELECT * FROM \"{table}\" LIMIT 10"
38
+ end
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
+
50
+ protected
51
+
52
+ def endpoint
53
+ @endpoint ||= client.info["version"]["number"].to_i >= 7 ? "_sql" : "_xpack/sql"
54
+ end
55
+
56
+ def client
57
+ @client ||= Elasticsearch::Client.new(url: settings["url"])
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ module Blazer
2
+ module Adapters
3
+ class HiveAdapter < BaseAdapter
4
+ def run_statement(statement, comment)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ result = client.execute("#{statement} /*#{comment}*/")
11
+ columns = result.any? ? result.first.keys : []
12
+ rows = result.map(&:values)
13
+ rescue => e
14
+ error = e.message
15
+ end
16
+
17
+ [columns, rows, error]
18
+ end
19
+
20
+ def tables
21
+ client.execute("SHOW TABLES").map { |r| r["tab_name"] }
22
+ end
23
+
24
+ def preview_statement
25
+ "SELECT * FROM {table} LIMIT 10"
26
+ end
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
+
38
+ protected
39
+
40
+ def client
41
+ @client ||= begin
42
+ uri = URI.parse(settings["url"])
43
+ Hexspace::Client.new(
44
+ host: uri.host,
45
+ port: uri.port,
46
+ username: uri.user,
47
+ password: uri.password,
48
+ database: uri.path.sub(/\A\//, ""),
49
+ mode: uri.scheme.to_sym
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,64 @@
1
+ module Blazer
2
+ module Adapters
3
+ class IgniteAdapter < BaseAdapter
4
+ def run_statement(statement, comment, bind_params)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ result = client.query("#{statement} /*#{comment}*/", bind_params, schema: default_schema, statement_type: :select, timeout: data_source.timeout)
11
+ columns = result.any? ? result.first.keys : []
12
+ rows = result.map(&:values)
13
+ rescue => e
14
+ error = e.message
15
+ end
16
+
17
+ [columns, rows, error]
18
+ end
19
+
20
+ def preview_statement
21
+ "SELECT * FROM {table} LIMIT 10"
22
+ end
23
+
24
+ def tables
25
+ sql = "SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema NOT IN ('INFORMATION_SCHEMA', 'SYS')"
26
+ result = data_source.run_statement(sql)
27
+ result.rows.reject { |row| row[1].start_with?("__") }.map do |row|
28
+ (row[0] == default_schema ? row[1] : "#{row[0]}.#{row[1]}").downcase
29
+ end
30
+ end
31
+
32
+ # TODO figure out error
33
+ # Table `__T0` can be accessed only within Ignite query context.
34
+ # def schema
35
+ # sql = "SELECT table_schema, table_name, column_name, data_type, ordinal_position FROM information_schema.columns WHERE table_schema NOT IN ('INFORMATION_SCHEMA', 'SYS')"
36
+ # result = data_source.run_statement(sql)
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
+ # end
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
+
50
+ private
51
+
52
+ def default_schema
53
+ "PUBLIC"
54
+ end
55
+
56
+ def client
57
+ @client ||= begin
58
+ uri = URI(settings["url"])
59
+ Ignite::Client.new(host: uri.host, port: uri.port, username: uri.user, password: uri.password)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,57 @@
1
+ module Blazer
2
+ module Adapters
3
+ class InfluxdbAdapter < BaseAdapter
4
+ def run_statement(statement, comment)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ result = client.query(statement, denormalize: false).first
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
24
+ end
25
+ end
26
+ rescue => e
27
+ error = e.message
28
+ end
29
+
30
+ [columns, rows, error]
31
+ end
32
+
33
+ def tables
34
+ client.list_series
35
+ end
36
+
37
+ def preview_statement
38
+ "SELECT * FROM {table} LIMIT 10"
39
+ end
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
+
50
+ protected
51
+
52
+ def client
53
+ @client ||= InfluxDB::Client.new(url: settings["url"])
54
+ end
55
+ end
56
+ end
57
+ end