sql-jarvis 1.8.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 (98) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/CHANGELOG.md +228 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +775 -0
  7. data/Rakefile +1 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  11. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  12. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  13. data/app/assets/javascripts/blazer/Chart.js +14145 -0
  14. data/app/assets/javascripts/blazer/Sortable.js +1144 -0
  15. data/app/assets/javascripts/blazer/ace.js +6 -0
  16. data/app/assets/javascripts/blazer/ace/ace.js +11 -0
  17. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
  18. data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
  19. data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
  20. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
  21. data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
  22. data/app/assets/javascripts/blazer/application.js +79 -0
  23. data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
  24. data/app/assets/javascripts/blazer/chartkick.js +1693 -0
  25. data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
  26. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  27. data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10308 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
  30. data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
  31. data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
  32. data/app/assets/javascripts/blazer/moment.js +3043 -0
  33. data/app/assets/javascripts/blazer/queries.js +110 -0
  34. data/app/assets/javascripts/blazer/routes.js +23 -0
  35. data/app/assets/javascripts/blazer/selectize.js +3667 -0
  36. data/app/assets/javascripts/blazer/stupidtable.js +114 -0
  37. data/app/assets/javascripts/blazer/vue.js +7515 -0
  38. data/app/assets/stylesheets/blazer/application.css +198 -0
  39. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
  40. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
  41. data/app/assets/stylesheets/blazer/github.css +125 -0
  42. data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
  43. data/app/controllers/blazer/base_controller.rb +103 -0
  44. data/app/controllers/blazer/checks_controller.rb +56 -0
  45. data/app/controllers/blazer/dashboards_controller.rb +105 -0
  46. data/app/controllers/blazer/queries_controller.rb +325 -0
  47. data/app/helpers/blazer/base_helper.rb +57 -0
  48. data/app/mailers/blazer/check_mailer.rb +27 -0
  49. data/app/models/blazer/audit.rb +6 -0
  50. data/app/models/blazer/check.rb +95 -0
  51. data/app/models/blazer/connection.rb +5 -0
  52. data/app/models/blazer/dashboard.rb +13 -0
  53. data/app/models/blazer/dashboard_query.rb +9 -0
  54. data/app/models/blazer/query.rb +31 -0
  55. data/app/models/blazer/record.rb +5 -0
  56. data/app/views/blazer/_nav.html.erb +16 -0
  57. data/app/views/blazer/_variables.html.erb +102 -0
  58. data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
  59. data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
  60. data/app/views/blazer/checks/_form.html.erb +71 -0
  61. data/app/views/blazer/checks/edit.html.erb +1 -0
  62. data/app/views/blazer/checks/index.html.erb +40 -0
  63. data/app/views/blazer/checks/new.html.erb +1 -0
  64. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  65. data/app/views/blazer/dashboards/edit.html.erb +1 -0
  66. data/app/views/blazer/dashboards/new.html.erb +1 -0
  67. data/app/views/blazer/dashboards/show.html.erb +47 -0
  68. data/app/views/blazer/queries/_form.html.erb +240 -0
  69. data/app/views/blazer/queries/edit.html.erb +2 -0
  70. data/app/views/blazer/queries/home.html.erb +152 -0
  71. data/app/views/blazer/queries/new.html.erb +2 -0
  72. data/app/views/blazer/queries/run.html.erb +163 -0
  73. data/app/views/blazer/queries/schema.html.erb +18 -0
  74. data/app/views/blazer/queries/show.html.erb +73 -0
  75. data/app/views/layouts/blazer/application.html.erb +24 -0
  76. data/blazer.gemspec +26 -0
  77. data/config/routes.rb +16 -0
  78. data/lib/blazer.rb +185 -0
  79. data/lib/blazer/adapters/athena_adapter.rb +128 -0
  80. data/lib/blazer/adapters/base_adapter.rb +53 -0
  81. data/lib/blazer/adapters/bigquery_adapter.rb +67 -0
  82. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  83. data/lib/blazer/adapters/elasticsearch_adapter.rb +49 -0
  84. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  85. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  86. data/lib/blazer/adapters/sql_adapter.rb +182 -0
  87. data/lib/blazer/data_source.rb +193 -0
  88. data/lib/blazer/detect_anomalies.R +19 -0
  89. data/lib/blazer/engine.rb +47 -0
  90. data/lib/blazer/result.rb +170 -0
  91. data/lib/blazer/run_statement.rb +40 -0
  92. data/lib/blazer/run_statement_job.rb +21 -0
  93. data/lib/blazer/version.rb +3 -0
  94. data/lib/generators/blazer/install_generator.rb +39 -0
  95. data/lib/generators/blazer/templates/config.yml +62 -0
  96. data/lib/generators/blazer/templates/install.rb +45 -0
  97. data/lib/tasks/blazer.rake +10 -0
  98. metadata +211 -0
@@ -0,0 +1,128 @@
1
+ module Blazer
2
+ module Adapters
3
+ class AthenaAdapter < BaseAdapter
4
+ def run_statement(statement, comment)
5
+ require "digest/md5"
6
+
7
+ columns = []
8
+ rows = []
9
+ error = nil
10
+
11
+ begin
12
+ resp =
13
+ client.start_query_execution(
14
+ query_string: statement,
15
+ # use token so we fetch cached results after query is run
16
+ client_request_token: Digest::MD5.hexdigest(statement),
17
+ query_execution_context: {
18
+ database: database,
19
+ },
20
+ result_configuration: {
21
+ output_location: settings["output_location"]
22
+ }
23
+ )
24
+ query_execution_id = resp.query_execution_id
25
+
26
+ timeout = data_source.timeout || 300
27
+ stop_at = Time.now + timeout
28
+ resp = nil
29
+
30
+ begin
31
+ resp = client.get_query_results(
32
+ query_execution_id: query_execution_id
33
+ )
34
+ rescue Aws::Athena::Errors::InvalidRequestException => e
35
+ if e.message != "Query has not yet finished. Current state: RUNNING"
36
+ raise e
37
+ end
38
+ if Time.now < stop_at
39
+ sleep(3)
40
+ retry
41
+ end
42
+ end
43
+
44
+ if resp && resp.result_set
45
+ column_info = resp.result_set.result_set_metadata.column_info
46
+ columns = column_info.map(&:name)
47
+ column_types = column_info.map(&:type)
48
+
49
+ untyped_rows = []
50
+
51
+ # paginated
52
+ resp.each do |page|
53
+ untyped_rows.concat page.result_set.rows.map { |r| r.data.map(&:var_char_value) }
54
+ end
55
+
56
+ utc = ActiveSupport::TimeZone['Etc/UTC']
57
+
58
+ rows = untyped_rows[1..-1] || []
59
+ column_types.each_with_index do |ct, i|
60
+ # TODO more column_types
61
+ case ct
62
+ when "timestamp"
63
+ rows.each do |row|
64
+ row[i] = utc.parse(row[i])
65
+ end
66
+ when "date"
67
+ rows.each do |row|
68
+ row[i] = Date.parse(row[i])
69
+ end
70
+ when "bigint"
71
+ rows.each do |row|
72
+ row[i] = row[i].to_i
73
+ end
74
+ when "double"
75
+ rows.each do |row|
76
+ row[i] = row[i].to_f
77
+ end
78
+ end
79
+ end
80
+ elsif resp
81
+ error = fetch_error(query_execution_id)
82
+ else
83
+ error = Blazer::TIMEOUT_MESSAGE
84
+ end
85
+ rescue Aws::Athena::Errors::InvalidRequestException => e
86
+ error = e.message
87
+ if error == "Query did not finish successfully. Final query state: FAILED"
88
+ error = fetch_error(query_execution_id)
89
+ end
90
+ end
91
+
92
+ [columns, rows, error]
93
+ end
94
+
95
+ def tables
96
+ glue.get_tables(database_name: database).table_list.map(&:name).sort
97
+ end
98
+
99
+ def schema
100
+ glue.get_tables(database_name: database).table_list.map { |t| {table: t.name, columns: t.storage_descriptor.columns.map { |c| {name: c.name, data_type: c.type} }} }
101
+ end
102
+
103
+ def preview_statement
104
+ "SELECT * FROM {table} LIMIT 10"
105
+ end
106
+
107
+ private
108
+
109
+ def database
110
+ @database ||= settings["database"] || "default"
111
+ end
112
+
113
+ def fetch_error(query_execution_id)
114
+ client.get_query_execution(
115
+ query_execution_id: query_execution_id
116
+ ).query_execution.status.state_change_reason
117
+ end
118
+
119
+ def client
120
+ @client ||= Aws::Athena::Client.new
121
+ end
122
+
123
+ def glue
124
+ @glue ||= Aws::Glue::Client.new
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,53 @@
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
+ # the one required method
12
+ end
13
+
14
+ def tables
15
+ [] # optional, but nice to have
16
+ end
17
+
18
+ def schema
19
+ [] # optional, but nice to have
20
+ end
21
+
22
+ def preview_statement
23
+ "" # also optional, but nice to have
24
+ end
25
+
26
+ def reconnect
27
+ # optional
28
+ end
29
+
30
+ def cost(statement)
31
+ # optional
32
+ end
33
+
34
+ def explain(statement)
35
+ # optional
36
+ end
37
+
38
+ def cancel(run_id)
39
+ # optional
40
+ end
41
+
42
+ def cachable?(statement)
43
+ true # optional
44
+ end
45
+
46
+ protected
47
+
48
+ def settings
49
+ @data_source.settings
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,67 @@
1
+ module Blazer
2
+ module Adapters
3
+ class BigQueryAdapter < BaseAdapter
4
+ def run_statement(statement, comment)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ options = {}
11
+ options[:timeout] = data_source.timeout.to_i * 1000 if data_source.timeout
12
+ results = bigquery.query(statement, options) # ms
13
+ if results.complete?
14
+ columns = results.first.keys.map(&:to_s) if results.size > 0
15
+ rows = results.map(&:values)
16
+ else
17
+ error = Blazer::TIMEOUT_MESSAGE
18
+ end
19
+ rescue => e
20
+ error = e.message
21
+ end
22
+
23
+ [columns, rows, error]
24
+ end
25
+
26
+ def tables
27
+ table_refs.map { |t| "#{t.project_id}.#{t.dataset_id}.#{t.table_id}" }
28
+ end
29
+
30
+ def schema
31
+ table_refs.map do |table_ref|
32
+ {
33
+ schema: table_ref.dataset_id,
34
+ table: table_ref.table_id,
35
+ columns: table_columns(table_ref)
36
+ }
37
+ end
38
+ end
39
+
40
+ def preview_statement
41
+ "SELECT * FROM `{table}` LIMIT 10"
42
+ end
43
+
44
+ private
45
+
46
+ def bigquery
47
+ @bigquery ||= begin
48
+ require "google/cloud/bigquery"
49
+ Google::Cloud::Bigquery.new(
50
+ project: settings["project"],
51
+ keyfile: settings["keyfile"]
52
+ )
53
+ end
54
+ end
55
+
56
+ def table_refs
57
+ bigquery.datasets.map(&:tables).flat_map { |table_list| table_list.map(&:table_ref) }
58
+ end
59
+
60
+ def table_columns(table_ref)
61
+ schema = bigquery.service.get_table(table_ref.dataset_id, table_ref.table_id).schema
62
+ return [] if schema.nil?
63
+ schema.fields.map { |field| {name: field.name, data_type: field.type} }
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,28 @@
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
+ private
22
+
23
+ def drill
24
+ @drill ||= ::Drill.new(url: settings["url"])
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,49 @@
1
+ module Blazer
2
+ module Adapters
3
+ class ElasticsearchAdapter < BaseAdapter
4
+ def run_statement(statement, comment)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ header, body = statement.gsub(/\/\/.+/, "").strip.split("\n", 2)
11
+ body = JSON.parse(body)
12
+ body["timeout"] ||= data_source.timeout if data_source.timeout
13
+ response = client.msearch(body: [JSON.parse(header), body])["responses"].first
14
+ if response["error"]
15
+ error = response["error"]
16
+ else
17
+ hits = response["hits"]["hits"]
18
+ source_keys = hits.flat_map { |r| r["_source"].keys }.uniq
19
+ hit_keys = (hits.first.try(:keys) || []) - ["_source"]
20
+ columns = source_keys + hit_keys
21
+ rows =
22
+ hits.map do |r|
23
+ source = r["_source"]
24
+ source_keys.map { |k| source[k] } + hit_keys.map { |k| r[k] }
25
+ end
26
+ end
27
+ rescue => e
28
+ error = e.message
29
+ end
30
+
31
+ [columns, rows, error]
32
+ end
33
+
34
+ def tables
35
+ client.indices.get_aliases(name: "*").map { |k, v| [k, v["aliases"].keys] }.flatten.uniq.sort
36
+ end
37
+
38
+ def preview_statement
39
+ %!// header\n{"index": "{table}"}\n\n// body\n{"query": {"match_all": {}}, "size": 10}!
40
+ end
41
+
42
+ protected
43
+
44
+ def client
45
+ @client ||= Elasticsearch::Client.new(url: settings["url"])
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ module Blazer
2
+ module Adapters
3
+ class MongodbAdapter < BaseAdapter
4
+ def run_statement(statement, comment)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ documents = db.command({:$eval => "#{statement.strip}.toArray()"}).documents.first["retval"]
11
+ columns = documents.flat_map { |r| r.keys }.uniq
12
+ rows = documents.map { |r| columns.map { |c| r[c] } }
13
+ rescue => e
14
+ error = e.message
15
+ end
16
+
17
+ [columns, rows, error]
18
+ end
19
+
20
+ def tables
21
+ db.collection_names
22
+ end
23
+
24
+ def preview_statement
25
+ "db.{table}.find().limit(10)"
26
+ end
27
+
28
+ protected
29
+
30
+ def client
31
+ @client ||= Mongo::Client.new(settings["url"], connect_timeout: 1, socket_timeout: 1, server_selection_timeout: 1)
32
+ end
33
+
34
+ def db
35
+ @db ||= client.database
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,45 @@
1
+ module Blazer
2
+ module Adapters
3
+ class PrestoAdapter < BaseAdapter
4
+ def run_statement(statement, comment)
5
+ columns = []
6
+ rows = []
7
+ error = nil
8
+
9
+ begin
10
+ columns, rows = client.run("#{statement} /*#{comment}*/")
11
+ columns = columns.map(&:name)
12
+ rescue => e
13
+ error = e.message
14
+ end
15
+
16
+ [columns, rows, error]
17
+ end
18
+
19
+ def tables
20
+ _, rows = client.run("SHOW TABLES")
21
+ rows.map(&:first)
22
+ end
23
+
24
+ def preview_statement
25
+ "SELECT * FROM {table} LIMIT 10"
26
+ end
27
+
28
+ protected
29
+
30
+ def client
31
+ @client ||= begin
32
+ uri = URI.parse(settings["url"])
33
+ query = uri.query ? CGI::parse(uri.query) : {}
34
+ Presto::Client.new(
35
+ server: "#{uri.host}:#{uri.port}",
36
+ catalog: uri.path.to_s.sub(/\A\//, ""),
37
+ schema: query["schema"] || "public",
38
+ user: uri.user,
39
+ http_debug: false
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,182 @@
1
+ module Blazer
2
+ module Adapters
3
+ class SqlAdapter < BaseAdapter
4
+ attr_reader :connection_model
5
+
6
+ def initialize(data_source)
7
+ super
8
+
9
+ @connection_model =
10
+ Class.new(Blazer::Connection) do
11
+ def self.name
12
+ "Blazer::Connection::Adapter#{object_id}"
13
+ end
14
+ establish_connection(data_source.settings["url"]) if data_source.settings["url"]
15
+ end
16
+ end
17
+
18
+ def run_statement(statement, comment)
19
+ columns = []
20
+ rows = []
21
+ error = nil
22
+
23
+ begin
24
+ in_transaction do
25
+ set_timeout(data_source.timeout) if data_source.timeout
26
+
27
+ result = select_all("#{statement} /*#{comment}*/")
28
+ columns = result.columns
29
+ cast_method = Rails::VERSION::MAJOR < 5 ? :type_cast : :cast_value
30
+ result.rows.each do |untyped_row|
31
+ rows << (result.column_types.empty? ? untyped_row : columns.each_with_index.map { |c, i| untyped_row[i] ? result.column_types[c].send(cast_method, untyped_row[i]) : untyped_row[i] })
32
+ end
33
+ end
34
+ rescue => e
35
+ error = e.message.sub(/.+ERROR: /, "")
36
+ error = Blazer::TIMEOUT_MESSAGE if Blazer::TIMEOUT_ERRORS.any? { |e| error.include?(e) }
37
+ reconnect if error.include?("PG::ConnectionBad")
38
+ end
39
+
40
+ [columns, rows, error]
41
+ end
42
+
43
+ def tables
44
+ result = data_source.run_statement(connection_model.send(:sanitize_sql_array, ["SELECT table_name FROM information_schema.tables WHERE table_schema IN (?) ORDER BY table_name", schemas]), refresh_cache: true)
45
+ result.rows.map(&:first)
46
+ end
47
+
48
+ def schema
49
+ result = data_source.run_statement(connection_model.send(:sanitize_sql_array, ["SELECT table_schema, table_name, column_name, data_type, ordinal_position FROM information_schema.columns WHERE table_schema IN (?) ORDER BY 1, 2", schemas]))
50
+ 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]} }} }
51
+ end
52
+
53
+ def preview_statement
54
+ if postgresql?
55
+ "SELECT * FROM \"{table}\" LIMIT 10"
56
+ elsif sqlserver?
57
+ "SELECT TOP (10) * FROM {table}"
58
+ else
59
+ "SELECT * FROM {table} LIMIT 10"
60
+ end
61
+ end
62
+
63
+ def reconnect
64
+ connection_model.establish_connection(settings["url"])
65
+ end
66
+
67
+ def cost(statement)
68
+ result = explain(statement)
69
+ if sqlserver?
70
+ result["TotalSubtreeCost"]
71
+ else
72
+ match = /cost=\d+\.\d+..(\d+\.\d+) /.match(result)
73
+ match[1] if match
74
+ end
75
+ end
76
+
77
+ def explain(statement)
78
+ if postgresql? || redshift?
79
+ select_all("EXPLAIN #{statement}").rows.first.first
80
+ elsif sqlserver?
81
+ begin
82
+ execute("SET SHOWPLAN_ALL ON")
83
+ result = select_all(statement).each.first
84
+ ensure
85
+ execute("SET SHOWPLAN_ALL OFF")
86
+ end
87
+ result
88
+ end
89
+ rescue
90
+ nil
91
+ end
92
+
93
+ def cancel(run_id)
94
+ if postgresql?
95
+ select_all("SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND query LIKE '%,run_id:#{run_id}%'")
96
+ elsif redshift?
97
+ first_row = select_all("SELECT pid FROM stv_recents WHERE status = 'Running' AND query LIKE '%,run_id:#{run_id}%'").first
98
+ if first_row
99
+ select_all("CANCEL #{first_row["pid"].to_i}")
100
+ end
101
+ end
102
+ end
103
+
104
+ def cachable?(statement)
105
+ !%w[CREATE ALTER UPDATE INSERT DELETE].include?(statement.split.first.to_s.upcase)
106
+ end
107
+
108
+ protected
109
+
110
+ def select_all(statement)
111
+ connection_model.connection.select_all(statement)
112
+ end
113
+
114
+ # seperate from select_all to prevent mysql error
115
+ def execute(statement)
116
+ connection_model.connection.execute(statement)
117
+ end
118
+
119
+ def postgresql?
120
+ ["PostgreSQL", "PostGIS"].include?(adapter_name)
121
+ end
122
+
123
+ def redshift?
124
+ ["Redshift"].include?(adapter_name)
125
+ end
126
+
127
+ def mysql?
128
+ ["MySQL", "Mysql2", "Mysql2Spatial"].include?(adapter_name)
129
+ end
130
+
131
+ def sqlserver?
132
+ ["SQLServer", "tinytds", "mssql"].include?(adapter_name)
133
+ end
134
+
135
+ def adapter_name
136
+ # prevent bad data source from taking down queries/new
137
+ connection_model.connection.adapter_name rescue nil
138
+ end
139
+
140
+ def schemas
141
+ settings["schemas"] || [connection_model.connection_config[:schema] || default_schema]
142
+ end
143
+
144
+ def default_schema
145
+ if postgresql? || redshift?
146
+ "public"
147
+ elsif sqlserver?
148
+ "dbo"
149
+ else
150
+ connection_model.connection_config[:database]
151
+ end
152
+ end
153
+
154
+ def set_timeout(timeout)
155
+ if postgresql? || redshift?
156
+ execute("SET #{use_transaction? ? "LOCAL " : ""}statement_timeout = #{timeout.to_i * 1000}")
157
+ elsif mysql?
158
+ execute("SET max_execution_time = #{timeout.to_i * 1000}")
159
+ else
160
+ raise Blazer::TimeoutNotSupported, "Timeout not supported for #{adapter_name} adapter"
161
+ end
162
+ end
163
+
164
+ def use_transaction?
165
+ settings.key?("use_transaction") ? settings["use_transaction"] : true
166
+ end
167
+
168
+ def in_transaction
169
+ connection_model.connection_pool.with_connection do
170
+ if use_transaction?
171
+ connection_model.transaction do
172
+ yield
173
+ raise ActiveRecord::Rollback
174
+ end
175
+ else
176
+ yield
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end