queries_controller.rb 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. module Blazer
  2. class QueriesController < ApplicationController
  3. # skip all filters
  4. skip_filter *_process_action_callbacks.map(&:filter)
  5. protect_from_forgery with: :exception
  6. if ENV["BLAZER_PASSWORD"]
  7. http_basic_authenticate_with name: ENV["BLAZER_USERNAME"], password: ENV["BLAZER_PASSWORD"]
  8. end
  9. layout "blazer/application"
  10. before_action :ensure_database_url
  11. before_action :set_query, only: [:show, :edit, :update, :destroy]
  12. def index
  13. @queries = Blazer::Query.order(:name)
  14. end
  15. def new
  16. @query = Blazer::Query.new(statement: params[:statement])
  17. end
  18. def create
  19. @query = Blazer::Query.new(query_params)
  20. @query.creator = current_user if respond_to?(:current_user)
  21. if @query.save
  22. redirect_to @query
  23. else
  24. render :new
  25. end
  26. end
  27. def show
  28. @statement = @query.statement
  29. process_vars(@statement)
  30. @smart_vars = {}
  31. @sql_errors = []
  32. @bind_vars.each do |var|
  33. query = smart_variables[var]
  34. if query
  35. rows, error = run_statement(query)
  36. @smart_vars[var] = rows.map{|v| v.values.reverse }
  37. @sql_errors << error if error
  38. end
  39. end
  40. end
  41. def edit
  42. end
  43. def run
  44. @statement = params[:statement]
  45. process_vars(@statement)
  46. if @success
  47. # audit
  48. if Blazer.audit
  49. audit = Blazer::Audit.new(statement: @statement)
  50. audit.user = current_user if respond_to?(:current_user)
  51. audit.save!
  52. end
  53. @rows, @error = run_statement(@statement)
  54. @columns = {}
  55. if @rows.any?
  56. @rows.first.each do |key, value|
  57. @columns[key] =
  58. case value
  59. when Integer
  60. "int"
  61. when Float
  62. "float"
  63. else
  64. "string-ins"
  65. end
  66. end
  67. end
  68. @query = Query.find_by(id: params[:query_id]) if params[:query_id]
  69. @filename = @query.name.parameterize if @query
  70. @min_width_types = (@rows.first || {}).select{|k, v| v.is_a?(Time) or v.is_a?(String) or smart_columns[k] }.keys
  71. @boom = {}
  72. @columns.keys.each do |key|
  73. query = smart_columns[key]
  74. if query
  75. values = @rows.map{|r| r[key] }.compact.uniq
  76. rows, error = run_statement(ActiveRecord::Base.send(:sanitize_sql_array, [query.sub("{value}", "(?)"), values]))
  77. @boom[key] = Hash[ rows.map(&:values) ]
  78. end
  79. end
  80. @linked_columns = linked_columns
  81. end
  82. respond_to do |format|
  83. format.html do
  84. render layout: false
  85. end
  86. format.csv do
  87. send_data csv_data(@rows), type: 'text/csv; charset=utf-8; header=present', disposition: "attachment; filename=\"#{@query ? @query.name.parameterize : "query"}.csv\""
  88. end
  89. end
  90. end
  91. def update
  92. if @query.update(query_params)
  93. redirect_to query_path(@query)
  94. else
  95. render :edit
  96. end
  97. end
  98. def destroy
  99. @query.destroy
  100. redirect_to root_url
  101. end
  102. private
  103. def ensure_database_url
  104. render text: "BLAZER_DATABASE_URL required" if !ENV["BLAZER_DATABASE_URL"]
  105. end
  106. def set_query
  107. @query = Blazer::Query.find(params[:id].to_s.split("-").first)
  108. end
  109. def query_params
  110. params.require(:query).permit(:name, :description, :statement)
  111. end
  112. def csv_data(rows)
  113. CSV.generate do |csv|
  114. if rows.any?
  115. csv << rows.first.keys
  116. end
  117. rows.each do |row|
  118. csv << row.values
  119. end
  120. end
  121. end
  122. def run_statement(statement)
  123. rows = []
  124. error = nil
  125. begin
  126. Blazer::Connection.transaction do
  127. result = Blazer::Connection.connection.select_all(statement)
  128. result.each do |untyped_row|
  129. row = {}
  130. untyped_row.each do |k, v|
  131. row[k] = result.column_types[k].type_cast(v)
  132. end
  133. rows << row
  134. end
  135. raise ActiveRecord::Rollback
  136. end
  137. rescue ActiveRecord::StatementInvalid => e
  138. error = e.message.sub(/.+ERROR: /, "")
  139. end
  140. [rows, error]
  141. end
  142. def extract_vars(statement)
  143. statement.scan(/\{.*?\}/).map{|v| v[1...-1] }.uniq
  144. end
  145. def process_vars(statement)
  146. @bind_vars = extract_vars(statement)
  147. @success = @bind_vars.all?{|v| params[v] }
  148. if @success
  149. @bind_vars.each do |var|
  150. value = params[var].presence
  151. value = value.to_i if value.to_i.to_s == value
  152. if var.end_with?("_at")
  153. value = Blazer.time_zone.parse(value) rescue nil
  154. end
  155. statement.gsub!("{#{var}}", ActiveRecord::Base.connection.quote(value))
  156. end
  157. end
  158. end
  159. def settings
  160. YAML.load(File.read(Rails.root.join("config", "blazer.yml")))
  161. end
  162. def linked_columns
  163. settings["linked_columns"] || {}
  164. end
  165. def smart_columns
  166. settings["smart_columns"] || {}
  167. end
  168. def smart_variables
  169. settings["smart_variables"] || {}
  170. end
  171. def tables
  172. default_schema = Blazer::Connection.connection.adapter_name == "PostgreSQL" ? "public" : Blazer::Connection.connection_config[:database]
  173. schema = Blazer::Connection.connection_config[:schema] || default_schema
  174. rows, error = run_statement(Blazer::Connection.send(:sanitize_sql_array, ["SELECT table_name, column_name, ordinal_position, data_type FROM information_schema.columns WHERE table_schema = ?", schema]))
  175. Hash[ rows.group_by{|r| r["table_name"] }.map{|t, f| [t, f.sort_by{|f| f["ordinal_position"] }.map{|f| f.slice("column_name", "data_type") }] }.sort_by{|t, f| t } ]
  176. end
  177. helper_method :tables
  178. end
  179. end