瀏覽代碼

Added caching

Andrew 9 年之前
父節點
當前提交
675a87a040

+ 1 - 0
CHANGELOG.md

@@ -1,6 +1,7 @@
 ## 1.0.1 [unreleased]
 
 - Added comments to queries
+- Added `cache` option
 - Added `user_method` option
 - Added `use_transaction` option
 

+ 1 - 1
app/controllers/blazer/dashboards_controller.rb

@@ -34,7 +34,7 @@ module Blazer
         data_sources.each do |data_source|
           query = data_source.smart_variables[var]
           if query
-            rows, error = data_source.run_statement(query)
+            rows, error, cached_at = data_source.run_statement(query)
             (@smart_vars[var] ||= []).concat rows.map { |v| v.values.reverse }
             @sql_errors << error if error
           end

+ 3 - 3
app/controllers/blazer/queries_controller.rb

@@ -36,7 +36,7 @@ module Blazer
       @bind_vars.each do |var|
         query = data_source.smart_variables[var]
         if query
-          rows, error = data_source.run_statement(query)
+          rows, error, cached_at = data_source.run_statement(query)
           @smart_vars[var] = rows.map { |v| v.values.reverse }
           @sql_errors << error if error
         end
@@ -67,7 +67,7 @@ module Blazer
         end
 
         @data_source = Blazer.data_sources[data_source]
-        @rows, @error = @data_source.run_statement(@statement, user: blazer_user, query: @query)
+        @rows, @error, @cached_at = @data_source.run_statement(@statement, user: blazer_user, query: @query)
 
         if @query && !@error.to_s.include?("canceling statement due to statement timeout")
           @query.checks.each do |check|
@@ -99,7 +99,7 @@ module Blazer
           query = @data_source.smart_columns[key]
           if query
             values = @rows.map { |r| r[key] }.compact.uniq
-            rows, error = @data_source.run_statement(ActiveRecord::Base.send(:sanitize_sql_array, [query.sub("{value}", "(?)"), values]))
+            rows, error, cached_at = @data_source.run_statement(ActiveRecord::Base.send(:sanitize_sql_array, [query.sub("{value}", "(?)"), values]))
             @boom[key] = Hash[rows.map(&:values)]
           end
         end

+ 3 - 0
app/views/blazer/queries/run.html.erb

@@ -8,6 +8,9 @@
   <% end %>
 <% else %>
   <% unless @only_chart %>
+    <% if @cached_at %>
+      <p class="text-muted" style="float: right;">Cached <%= time_ago_in_words(@cached_at) %> ago</p>
+    <% end %>
     <p class="text-muted"><%= pluralize(@rows.size, "row") %></p>
   <% end %>
   <% if @rows.any? %>

+ 2 - 1
lib/blazer.rb

@@ -13,6 +13,7 @@ module Blazer
     attr_accessor :user_class
     attr_accessor :user_method
     attr_accessor :from_email
+    attr_accessor :cache
   end
   self.audit = true
   self.user_name = :name
@@ -44,7 +45,7 @@ module Blazer
       tries = 0
       # try 3 times on timeout errors
       begin
-        rows, error = data_sources[check.query.data_source].run_statement(check.query.statement)
+        rows, error, cached_at = data_sources[check.query.data_source].run_statement(check.query.statement)
         tries += 1
       end while error && error.include?("canceling statement due to statement timeout") && tries < 3
       check.update_state(rows, error)

+ 43 - 24
lib/blazer/data_source.rb

@@ -1,3 +1,5 @@
+require "digest/md5"
+
 module Blazer
   class DataSource
     attr_reader :id, :settings, :connection_model
@@ -34,48 +36,65 @@ module Blazer
       settings["timeout"]
     end
 
+    def cache
+      settings["cache"]
+    end
+
     def use_transaction?
       settings.key?("use_transaction") ? settings["use_transaction"] : true
     end
 
     def run_statement(statement, options = {})
-      rows = []
+      rows = nil
       error = nil
-      comment = "blazer"
-      if options[:user].respond_to?(:id)
-        comment << ",user_id:#{options[:user].id}"
-      end
-      if options[:user].respond_to?(Blazer.user_name)
-        # only include letters, numbers, and spaces to prevent injection
-        comment << ",user_name:#{options[:user].send(Blazer.user_name).to_s.gsub(/[^a-zA-Z0-9 ]/, "")}"
-      end
-      if options[:query].respond_to?(:id)
-        comment << ",query_id:#{options[:query].id}"
+      cached_at = nil
+      if cache
+        cache_key = ["blazer", "v2", id, Digest::MD5.hexdigest(statement)].join("/")
+        value = Blazer.cache.read(cache_key)
+        rows, cached_at = Marshal.load(value) if value
       end
 
-      in_transaction do
-        begin
-          connection_model.connection.execute("SET statement_timeout = #{timeout.to_i * 1000}") if timeout && postgresql?
-          result = connection_model.connection.select_all("#{statement} /*#{comment}*/")
-          result.each do |untyped_row|
-            row = {}
-            untyped_row.each do |k, v|
-              row[k] = result.column_types.empty? ? v : result.column_types[k].send(:type_cast, v)
+      unless rows
+        rows = []
+
+        comment = "blazer"
+        if options[:user].respond_to?(:id)
+          comment << ",user_id:#{options[:user].id}"
+        end
+        if options[:user].respond_to?(Blazer.user_name)
+          # only include letters, numbers, and spaces to prevent injection
+          comment << ",user_name:#{options[:user].send(Blazer.user_name).to_s.gsub(/[^a-zA-Z0-9 ]/, "")}"
+        end
+        if options[:query].respond_to?(:id)
+          comment << ",query_id:#{options[:query].id}"
+        end
+
+        in_transaction do
+          begin
+            connection_model.connection.execute("SET statement_timeout = #{timeout.to_i * 1000}") if timeout && postgresql?
+            result = connection_model.connection.select_all("#{statement} /*#{comment}*/")
+            result.each do |untyped_row|
+              row = {}
+              untyped_row.each do |k, v|
+                row[k] = result.column_types.empty? ? v : result.column_types[k].send(:type_cast, v)
+              end
+              rows << row
             end
-            rows << row
+          rescue ActiveRecord::StatementInvalid => e
+            error = e.message.sub(/.+ERROR: /, "")
           end
-        rescue ActiveRecord::StatementInvalid => e
-          error = e.message.sub(/.+ERROR: /, "")
         end
+
+        Blazer.cache.write(cache_key, Marshal.dump([rows, Time.now]), expires_in: cache.to_f * 60) if !error && cache
       end
 
-      [rows, error]
+      [rows, error, cached_at]
     end
 
     def tables
       default_schema = postgresql? ? "public" : connection_model.connection_config[:database]
       schema = connection_model.connection_config[:schema] || default_schema
-      rows, error = run_statement(connection_model.send(:sanitize_sql_array, ["SELECT table_name, column_name, ordinal_position, data_type FROM information_schema.columns WHERE table_schema = ?", schema]))
+      rows, error, cached_at = run_statement(connection_model.send(:sanitize_sql_array, ["SELECT table_name, column_name, ordinal_position, data_type FROM information_schema.columns WHERE table_schema = ?", schema]))
       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 }]
     end
 

+ 2 - 0
lib/blazer/engine.rb

@@ -18,6 +18,8 @@ module Blazer
       end
 
       Blazer::Query.belongs_to :creator, class_name: Blazer.user_class.to_s if Blazer.user_class
+
+      Blazer.cache ||= Rails.cache
     end
   end
 end