aboutsummaryrefslogtreecommitdiffstats
path: root/lib/arel/select_manager.rb
blob: 0f70a461a31fcfc87c62b05e1b9051ff5f219d73 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
module Arel
  class SelectManager < Arel::TreeManager
    include Arel::Crud

    def initialize engine
      super
      @head   = Nodes::SelectStatement.new
      @ctx    = @head.cores.last
    end

    def taken
      @head.limit
    end

    def skip amount
      @head.offset = Nodes::Offset.new(amount)
      self
    end

    def where_clauses
      warn "where_clauses is deprecated" if $VERBOSE
      to_sql = Visitors::ToSql.new @engine
      @ctx.wheres.map { |c| to_sql.accept c }
    end

    def lock locking = true
      # FIXME: do we even need to store this?  If locking is +false+ shouldn't
      # we just remove the node from the AST?
      @head.lock = Nodes::Lock.new
      self
    end

    def locked
      @head.lock
    end

    def on *exprs
      @ctx.froms.last.constraint = 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

        @ctx.groups.push Nodes::Group.new column
      end
      self
    end

    def from table
      if String === table
        return self if @ctx.froms.any? { |x| x.name.to_s == table }
      end

      @ctx.froms << table
      self
    end

    def join relation, klass = Nodes::InnerJoin
      return self unless relation

      case relation
      when String, Nodes::SqlLiteral
        raise if relation.blank?
        from Nodes::StringJoin.new(@ctx.froms.pop, relation)
      else
        from klass.new(@ctx.froms.pop, relation, nil)
      end
    end

    def having expr
      expr = Nodes::SqlLiteral.new(expr) if String === expr

      @ctx.having = Nodes::Having.new(expr)
      self
    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 == x.class ? SqlLiteral.new(x) : x
      }
      self
    end

    def where expr
      @ctx.wheres << expr
      self
    end

    def order *expr
      # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
      @head.orders.concat expr.map { |x|
        String === x ? Nodes::SqlLiteral.new(x) : x
      }
      self
    end

    def orders
      @head.orders
    end

    def wheres
      Compatibility::Wheres.new @engine, @ctx.wheres
    end

    def take limit
      @head.limit = limit
      self
    end

    def join_sql
      viz = Visitors::JoinSql.new @engine
      Nodes::SqlLiteral.new viz.accept @ctx
    end

    def order_clauses
      Visitors::OrderClauses.new(@engine).accept(@head).map { |x|
        Nodes::SqlLiteral.new x
      }
    end

    def joins manager
      manager.join_sql
    end

    def to_a
      raise NotImplementedError
    end

    # FIXME: this method should go away
    def insert values
      im = InsertManager.new @engine
      im.into @ctx.froms.last
      im.insert values
      @engine.connection.insert im.to_sql
    end

    private
    def collapse exprs
      return exprs.first if exprs.length == 1

      right = exprs.pop
      left  = exprs.pop

      right = Nodes::And.new left, right
      exprs.reverse.inject(right) { |memo,expr| Nodes::And.new(expr, memo) }
    end
  end
end