Class: SlimScrooge::Callsite

Inherits:
Object
  • Object
show all
Defined in:
lib/slim_scrooge/callsite.rb

Overview

A Callsite contains the list of columns that are accessed when an SQL query is made from a particular place in the app

Constant Summary collapse

ScroogeComma =
",".freeze
ScroogeRegexJoin =
/(?:LEFT|INNER|OUTER|CROSS)*\s*(?:STRAIGHT_JOIN|JOIN)/i

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model_class) ⇒ Callsite

Returns a new instance of Callsite.



43
44
45
46
47
48
49
50
51
52
# File 'lib/slim_scrooge/callsite.rb', line 43

def initialize(model_class)
  @all_columns = SimpleSet.new(model_class.column_names)
  @model_class_name = model_class.to_s
  @quoted_table_name = model_class.quoted_table_name
  @primary_key = model_class.primary_key
  @quoted_primary_key = model_class.connection.quote_column_name(@primary_key)
  @columns_hash = model_class.columns_hash
  @select_regexp = self.class.select_regexp(model_class.table_name)
  @seen_columns = SimpleSet.new(essential_columns(model_class))
end

Instance Attribute Details

#columns_hashObject (readonly)

Returns the value of attribute columns_hash.



12
13
14
# File 'lib/slim_scrooge/callsite.rb', line 12

def columns_hash
  @columns_hash
end

#model_class_nameObject (readonly)

Returns the value of attribute model_class_name.



12
13
14
# File 'lib/slim_scrooge/callsite.rb', line 12

def model_class_name
  @model_class_name
end

#primary_keyObject (readonly)

Returns the value of attribute primary_key.



12
13
14
# File 'lib/slim_scrooge/callsite.rb', line 12

def primary_key
  @primary_key
end

#seen_columnsObject

Returns the value of attribute seen_columns.



11
12
13
# File 'lib/slim_scrooge/callsite.rb', line 11

def seen_columns
  @seen_columns
end

Class Method Details

.make_callsite(model_class, original_sql) ⇒ Object

Make a callsite if the query is of the right type for us to optimise



17
18
19
20
21
22
23
# File 'lib/slim_scrooge/callsite.rb', line 17

def make_callsite(model_class, original_sql)
  if use_scrooge?(model_class, original_sql)
    new(model_class)
  else
    nil
  end
end

.select_regexp(table_name) ⇒ Object

The regexp that enables us to replace the * from SELECT * with the list of columns we actually need



38
39
40
# File 'lib/slim_scrooge/callsite.rb', line 38

def select_regexp(table_name)
  %r{SELECT +(?:[`"]?#{table_name}[`"]?.)?\* +FROM}
end

.use_scrooge?(model_class, original_sql) ⇒ Boolean

Check if query can be optimised

Returns:

  • (Boolean)


27
28
29
30
31
32
33
# File 'lib/slim_scrooge/callsite.rb', line 27

def use_scrooge?(model_class, original_sql)
  original_sql = original_sql.to_sql if original_sql.respond_to?(:to_sql)

  original_sql =~ select_regexp(model_class.table_name) && 
  model_class.columns_hash.has_key?(model_class.primary_key) && 
  original_sql !~ ScroogeRegexJoin
end

Instance Method Details

#connectionObject



98
99
100
# File 'lib/slim_scrooge/callsite.rb', line 98

def connection
  @model_class_name.constantize.connection
end

#essential_columns(model_class) ⇒ Object

List of columns that should always be fetched no matter what



56
57
58
59
60
61
62
63
# File 'lib/slim_scrooge/callsite.rb', line 56

def essential_columns(model_class)
  model_class.reflect_on_all_associations.inject([@primary_key]) do |arr, assoc|
    if assoc.options[:dependent] && assoc.macro == :belongs_to
      arr << assoc.respond_to?(:foreign_key) ? assoc.foreign_key : assoc.primary_key_name
    end
    arr
  end
end

#missing_columns(fetched_columns) ⇒ Object

List if columns what were not fetched



85
86
87
# File 'lib/slim_scrooge/callsite.rb', line 85

def missing_columns(fetched_columns)
  (@all_columns - SimpleSet.new(fetched_columns)) << @primary_key
end

#reload_sql(primary_keys, fetched_columns) ⇒ Object

Returns sql for fetching the unfetched columns for all the rows in the result set, specified by primary_keys



92
93
94
95
96
# File 'lib/slim_scrooge/callsite.rb', line 92

def reload_sql(primary_keys, fetched_columns)
  sql_keys = primary_keys.collect{|pk| "#{connection.quote_column_name(pk)}"}.join(ScroogeComma)
  cols = scrooge_select_sql(missing_columns(fetched_columns))
  "SELECT #{cols} FROM #{@quoted_table_name} WHERE #{@quoted_primary_key} IN (#{sql_keys})"
end

#scrooge_select_sql(set) ⇒ Object

Change a set of columns into a correctly quoted comma separated list



104
105
106
107
108
# File 'lib/slim_scrooge/callsite.rb', line 104

def scrooge_select_sql(set)
  set.collect do |name|
    "#{@quoted_table_name}.#{connection.quote_column_name(name)}"
  end.join(ScroogeComma)
end

#scrooged_sql(seen_columns, sql) ⇒ Object

Returns suitable sql given a list of columns and the original query



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/slim_scrooge/callsite.rb', line 67

def scrooged_sql(seen_columns, sql)
  if sql.respond_to?(:project)
    # modify the query - this is a hack that needs to be fixed
    projections = sql.instance_variable_get(:@ctx).projections
    select_cols = seen_columns.collect do |name|
      arel_attr = projections[0].dup
      arel_attr.name = name
      arel_attr
    end
    projections.replace(select_cols)
    sql
  else
    sql.gsub(@select_regexp, "SELECT #{scrooge_select_sql(seen_columns)} FROM")
  end
end