mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
cd395b9419
This is ultimately messy no matter what, and increases the coupling to the database backend, but we can at least contain it somewhat into an object that's already coupled.
268 lines
5.4 KiB
Ruby
268 lines
5.4 KiB
Ruby
require 'arel/collectors/sql_string'
|
|
|
|
module Arel
|
|
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 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 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 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)
|
|
@ctx.top = Nodes::Top.new(limit)
|
|
else
|
|
@ast.limit = nil
|
|
@ctx.top = nil
|
|
end
|
|
self
|
|
end
|
|
alias limit= take
|
|
|
|
def join_sources
|
|
@ctx.source.right
|
|
end
|
|
|
|
def source
|
|
@ctx.source
|
|
end
|
|
|
|
class Row < Struct.new(:data) # :nodoc:
|
|
def id
|
|
data['id']
|
|
end
|
|
|
|
def method_missing(name, *args)
|
|
name = name.to_s
|
|
return data[name] if data.key?(name)
|
|
super
|
|
end
|
|
end
|
|
|
|
private
|
|
def collapse exprs, existing = nil
|
|
exprs = exprs.unshift(existing.expr) if existing
|
|
exprs = exprs.compact.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
|