1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/lib/arel/select_manager.rb
Ryuta Kamizono 97347d8c40 Support Optimizer Hints
We as Arm Treasure Data are using Optimizer Hints with a monkey patch
(https://gist.github.com/kamipo/4c8539f0ce4acf85075cf5a6b0d9712e),
especially in order to use `MAX_EXECUTION_TIME` (refer #31129).

Example:

```ruby
class Job < ApplicationRecord
  default_scope { optimizer_hints("MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(jobs)") }
end
```

Optimizer Hints is supported not only for MySQL but also for most
databases (PostgreSQL on RDS, Oracle, SQL Server, etc), it is really
helpful to turn heavy queries for large scale applications.
2019-03-16 18:44:55 +09:00

266 lines
5.4 KiB
Ruby

# frozen_string_literal: true
module Arel # :nodoc: all
class SelectManager < Arel::TreeManager
include Arel::Crud
STRING_OR_SYMBOL_CLASS = [Symbol, String]
def initialize(table = nil)
super()
@ast = Nodes::SelectStatement.new
@ctx = @ast.cores.last
from table
end
def initialize_copy(other)
super
@ctx = @ast.cores.last
end
def limit
@ast.limit && @ast.limit.expr
end
alias :taken :limit
def constraints
@ctx.wheres
end
def offset
@ast.offset && @ast.offset.expr
end
def skip(amount)
if amount
@ast.offset = Nodes::Offset.new(amount)
else
@ast.offset = nil
end
self
end
alias :offset= :skip
###
# Produces an Arel::Nodes::Exists node
def exists
Arel::Nodes::Exists.new @ast
end
def as(other)
create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other)
end
def lock(locking = Arel.sql("FOR UPDATE"))
case locking
when true
locking = Arel.sql("FOR UPDATE")
when Arel::Nodes::SqlLiteral
when String
locking = Arel.sql locking
end
@ast.lock = Nodes::Lock.new(locking)
self
end
def locked
@ast.lock
end
def on(*exprs)
@ctx.source.right.last.right = Nodes::On.new(collapse(exprs))
self
end
def group(*columns)
columns.each do |column|
# FIXME: backwards compat
column = Nodes::SqlLiteral.new(column) if String === column
column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
@ctx.groups.push Nodes::Group.new column
end
self
end
def from(table)
table = Nodes::SqlLiteral.new(table) if String === table
case table
when Nodes::Join
@ctx.source.right << table
else
@ctx.source.left = table
end
self
end
def froms
@ast.cores.map { |x| x.from }.compact
end
def join(relation, klass = Nodes::InnerJoin)
return self unless relation
case relation
when String, Nodes::SqlLiteral
raise EmptyJoinError if relation.empty?
klass = Nodes::StringJoin
end
@ctx.source.right << create_join(relation, nil, klass)
self
end
def outer_join(relation)
join(relation, Nodes::OuterJoin)
end
def having(expr)
@ctx.havings << expr
self
end
def window(name)
window = Nodes::NamedWindow.new(name)
@ctx.windows.push window
window
end
def project(*projections)
# FIXME: converting these to SQLLiterals is probably not good, but
# rails tests require it.
@ctx.projections.concat projections.map { |x|
STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
}
self
end
def projections
@ctx.projections
end
def projections=(projections)
@ctx.projections = projections
end
def optimizer_hints(*hints)
unless hints.empty?
@ctx.optimizer_hints = Arel::Nodes::OptimizerHints.new(hints)
end
self
end
def distinct(value = true)
if value
@ctx.set_quantifier = Arel::Nodes::Distinct.new
else
@ctx.set_quantifier = nil
end
self
end
def distinct_on(value)
if value
@ctx.set_quantifier = Arel::Nodes::DistinctOn.new(value)
else
@ctx.set_quantifier = nil
end
self
end
def order(*expr)
# FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
@ast.orders.concat expr.map { |x|
STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
}
self
end
def orders
@ast.orders
end
def where_sql(engine = Table.engine)
return if @ctx.wheres.empty?
viz = Visitors::WhereSql.new(engine.connection.visitor, engine.connection)
Nodes::SqlLiteral.new viz.accept(@ctx, Collectors::SQLString.new).value
end
def union(operation, other = nil)
if other
node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
else
other = operation
node_class = Nodes::Union
end
node_class.new self.ast, other.ast
end
def intersect(other)
Nodes::Intersect.new ast, other.ast
end
def except(other)
Nodes::Except.new ast, other.ast
end
alias :minus :except
def lateral(table_name = nil)
base = table_name.nil? ? ast : as(table_name)
Nodes::Lateral.new(base)
end
def with(*subqueries)
if subqueries.first.is_a? Symbol
node_class = Nodes.const_get("With#{subqueries.shift.to_s.capitalize}")
else
node_class = Nodes::With
end
@ast.with = node_class.new(subqueries.flatten)
self
end
def take(limit)
if limit
@ast.limit = Nodes::Limit.new(limit)
else
@ast.limit = nil
end
self
end
alias limit= take
def join_sources
@ctx.source.right
end
def source
@ctx.source
end
private
def collapse(exprs)
exprs = exprs.compact
exprs.map! { |expr|
if String === expr
# FIXME: Don't do this automatically
Arel.sql(expr)
else
expr
end
}
if exprs.length == 1
exprs.first
else
create_and exprs
end
end
end
end