arel 0.4.0 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/README.markdown +24 -0
  2. data/lib/arel.rb +3 -1
  3. data/lib/arel/algebra/attributes/attribute.rb +175 -141
  4. data/lib/arel/algebra/core_extensions.rb +0 -1
  5. data/lib/arel/algebra/core_extensions/hash.rb +5 -9
  6. data/lib/arel/algebra/core_extensions/object.rb +0 -4
  7. data/lib/arel/algebra/expression.rb +37 -24
  8. data/lib/arel/algebra/header.rb +5 -6
  9. data/lib/arel/algebra/ordering.rb +13 -5
  10. data/lib/arel/algebra/predicates.rb +143 -27
  11. data/lib/arel/algebra/relations.rb +0 -1
  12. data/lib/arel/algebra/relations/operations/from.rb +10 -2
  13. data/lib/arel/algebra/relations/operations/group.rb +8 -6
  14. data/lib/arel/algebra/relations/operations/having.rb +3 -6
  15. data/lib/arel/algebra/relations/operations/join.rb +52 -18
  16. data/lib/arel/algebra/relations/operations/lock.rb +4 -6
  17. data/lib/arel/algebra/relations/operations/order.rb +11 -7
  18. data/lib/arel/algebra/relations/operations/project.rb +10 -10
  19. data/lib/arel/algebra/relations/operations/skip.rb +10 -3
  20. data/lib/arel/algebra/relations/operations/take.rb +10 -3
  21. data/lib/arel/algebra/relations/operations/where.rb +12 -6
  22. data/lib/arel/algebra/relations/relation.rb +161 -92
  23. data/lib/arel/algebra/relations/row.rb +8 -5
  24. data/lib/arel/algebra/relations/utilities/compound.rb +34 -33
  25. data/lib/arel/algebra/relations/utilities/externalization.rb +10 -8
  26. data/lib/arel/algebra/relations/writes.rb +24 -13
  27. data/lib/arel/algebra/value.rb +41 -2
  28. data/lib/arel/engines/memory.rb +0 -2
  29. data/lib/arel/engines/memory/engine.rb +3 -9
  30. data/lib/arel/engines/memory/relations.rb +0 -3
  31. data/lib/arel/engines/memory/relations/array.rb +5 -3
  32. data/lib/arel/engines/memory/relations/operations.rb +2 -60
  33. data/lib/arel/engines/sql.rb +0 -2
  34. data/lib/arel/engines/sql/christener.rb +12 -6
  35. data/lib/arel/engines/sql/compilers/oracle_compiler.rb +34 -23
  36. data/lib/arel/engines/sql/compilers/postgresql_compiler.rb +23 -15
  37. data/lib/arel/engines/sql/engine.rb +19 -27
  38. data/lib/arel/engines/sql/formatters.rb +26 -10
  39. data/lib/arel/engines/sql/relations.rb +0 -7
  40. data/lib/arel/engines/sql/relations/compiler.rb +70 -35
  41. data/lib/arel/engines/sql/relations/table.rb +44 -32
  42. data/lib/arel/{engines/sql/relations/utilities/recursion.rb → recursion/base_case.rb} +0 -0
  43. data/lib/arel/session.rb +24 -40
  44. data/lib/arel/sql_literal.rb +13 -0
  45. data/lib/arel/version.rb +1 -1
  46. data/spec/algebra/unit/predicates/inequality_spec.rb +32 -0
  47. data/spec/algebra/unit/predicates/predicate_spec.rb +22 -0
  48. data/spec/algebra/unit/primitives/attribute_spec.rb +3 -9
  49. data/spec/algebra/unit/primitives/expression_spec.rb +1 -7
  50. data/spec/algebra/unit/relations/join_spec.rb +0 -7
  51. data/spec/algebra/unit/relations/project_spec.rb +3 -3
  52. data/spec/algebra/unit/relations/relation_spec.rb +74 -25
  53. data/spec/algebra/unit/session/session_spec.rb +7 -7
  54. data/spec/engines/memory/integration/joins/cross_engine_spec.rb +20 -10
  55. data/spec/engines/memory/unit/relations/array_spec.rb +6 -5
  56. data/spec/engines/memory/unit/relations/join_spec.rb +7 -6
  57. data/spec/engines/memory/unit/relations/order_spec.rb +7 -6
  58. data/spec/engines/memory/unit/relations/project_spec.rb +6 -6
  59. data/spec/engines/memory/unit/relations/skip_spec.rb +10 -5
  60. data/spec/engines/memory/unit/relations/take_spec.rb +7 -5
  61. data/spec/engines/memory/unit/relations/where_spec.rb +13 -9
  62. data/spec/engines/sql/unit/engine_spec.rb +20 -0
  63. data/spec/engines/sql/unit/relations/group_spec.rb +2 -2
  64. data/spec/engines/sql/unit/relations/order_spec.rb +5 -5
  65. data/spec/engines/sql/unit/relations/project_spec.rb +4 -4
  66. data/spec/engines/sql/unit/relations/table_spec.rb +0 -7
  67. data/spec/engines/sql/unit/relations/take_spec.rb +26 -0
  68. data/spec/engines/sql/unit/relations/where_spec.rb +1 -1
  69. data/spec/spec_helper.rb +1 -4
  70. data/spec/sql/christener_spec.rb +70 -0
  71. data/spec/support/model.rb +7 -2
  72. metadata +109 -23
  73. data/lib/arel/algebra/core_extensions/class.rb +0 -32
  74. data/lib/arel/algebra/relations/operations/alias.rb +0 -7
  75. data/lib/arel/engines/memory/predicates.rb +0 -99
  76. data/lib/arel/engines/memory/primitives.rb +0 -27
  77. data/lib/arel/engines/memory/relations/compound.rb +0 -9
  78. data/lib/arel/engines/memory/relations/writes.rb +0 -7
  79. data/lib/arel/engines/sql/predicates.rb +0 -103
  80. data/lib/arel/engines/sql/primitives.rb +0 -97
  81. data/lib/arel/engines/sql/relations/operations/alias.rb +0 -5
  82. data/lib/arel/engines/sql/relations/operations/join.rb +0 -33
  83. data/lib/arel/engines/sql/relations/relation.rb +0 -65
  84. data/lib/arel/engines/sql/relations/utilities/compound.rb +0 -10
  85. data/lib/arel/engines/sql/relations/utilities/externalization.rb +0 -14
  86. data/lib/arel/engines/sql/relations/writes.rb +0 -19
@@ -4,7 +4,6 @@ require 'arel/algebra/relations/utilities/nil'
4
4
  require 'arel/algebra/relations/utilities/externalization'
5
5
  require 'arel/algebra/relations/row'
6
6
  require 'arel/algebra/relations/writes'
7
- require 'arel/algebra/relations/operations/alias'
8
7
  require 'arel/algebra/relations/operations/from'
9
8
  require 'arel/algebra/relations/operations/group'
10
9
  require 'arel/algebra/relations/operations/having'
@@ -1,6 +1,14 @@
1
1
  module Arel
2
2
  class From < Compound
3
- attributes :relation, :sources
4
- deriving :initialize, :==
3
+ attr_reader :sources
4
+
5
+ def initialize relation, sources
6
+ super(relation)
7
+ @sources = sources
8
+ end
9
+
10
+ def eval
11
+ unoperated_rows[sources..-1]
12
+ end
5
13
  end
6
14
  end
@@ -1,12 +1,14 @@
1
1
  module Arel
2
2
  class Group < Compound
3
- attributes :relation, :groupings
4
- deriving :==
3
+ attr_reader :groupings
5
4
 
6
- def initialize(relation, *groupings, &block)
7
- @relation = relation
8
- @groupings = (groupings + arguments_from_block(relation, &block)) \
9
- .collect { |g| g.bind(relation) }
5
+ def initialize(relation, groupings)
6
+ super(relation)
7
+ @groupings = groupings.collect { |g| g.bind(relation) }
8
+ end
9
+
10
+ def eval
11
+ raise NotImplementedError
10
12
  end
11
13
  end
12
14
  end
@@ -1,13 +1,10 @@
1
1
  module Arel
2
2
  class Having < Compound
3
- attributes :relation, :predicates
4
- deriving :==
5
- requires :restricting
3
+ attr_reader :predicates
6
4
 
7
- def initialize(relation, *predicates, &block)
8
- predicates = [yield(relation)] + predicates if block_given?
5
+ def initialize(relation, predicates)
6
+ super(relation)
9
7
  @predicates = predicates.map { |p| p.bind(relation) }
10
- @relation = relation
11
8
  end
12
9
 
13
10
  def havings
@@ -2,20 +2,17 @@ module Arel
2
2
  class Join
3
3
  include Relation
4
4
 
5
- attributes :relation1, :relation2, :predicates
6
- deriving :==
7
- delegate :name, :to => :relation1
5
+ attr_reader :relation1, :relation2, :predicates
8
6
 
9
7
  def initialize(relation1, relation2 = Nil.instance, *predicates)
10
- @relation1, @relation2, @predicates = relation1, relation2, predicates
8
+ @relation1 = relation1
9
+ @relation2 = relation2
10
+ @predicates = predicates
11
+ @attributes = nil
11
12
  end
12
13
 
13
- def hash
14
- @hash ||= :relation1.hash
15
- end
16
-
17
- def eql?(other)
18
- self == other
14
+ def name
15
+ relation1.name
19
16
  end
20
17
 
21
18
  def attributes
@@ -43,11 +40,54 @@ module Arel
43
40
  def engine
44
41
  relation1.engine != relation2.engine ? Memory::Engine.new : relation1.engine
45
42
  end
43
+
44
+ def table_sql(formatter = Sql::TableReference.new(self))
45
+ relation1.externalize.table_sql(formatter)
46
+ end
47
+
48
+ def joins(environment, formatter = Sql::TableReference.new(environment))
49
+ @joins ||= begin
50
+ this_join = [
51
+ join_sql,
52
+ relation2.externalize.table_sql(formatter),
53
+ ("ON" unless predicates.blank?),
54
+ (ons + relation2.externalize.wheres).collect { |p| p.bind(environment.relation).to_sql(Sql::WhereClause.new(environment)) }.join(' AND ')
55
+ ].compact.join(" ")
56
+ [relation1.joins(environment), this_join, relation2.joins(environment)].compact.join(" ")
57
+ end
58
+ end
59
+
60
+ def eval
61
+ result = []
62
+ relation1.call.each do |row1|
63
+ relation2.call.each do |row2|
64
+ combined_row = row1.combine(row2, self)
65
+ if predicates.all? { |p| p.eval(combined_row) }
66
+ result << combined_row
67
+ end
68
+ end
69
+ end
70
+ result
71
+ end
72
+
73
+ def to_sql(formatter = nil)
74
+ compiler.select_sql
75
+ end
76
+ end
77
+
78
+ class InnerJoin < Join
79
+ def join_sql; "INNER JOIN" end
80
+ end
81
+
82
+ class OuterJoin < Join
83
+ def join_sql; "LEFT OUTER JOIN" end
46
84
  end
47
85
 
48
- class InnerJoin < Join; end
49
- class OuterJoin < Join; end
50
86
  class StringJoin < Join
87
+ def joins(environment, formatter = Sql::TableReference.new(environment))
88
+ [relation1.joins(environment), relation2].compact.join(" ")
89
+ end
90
+
51
91
  def externalizable?
52
92
  relation1.externalizable?
53
93
  end
@@ -60,10 +100,4 @@ module Arel
60
100
  relation1.engine
61
101
  end
62
102
  end
63
-
64
- module Relation
65
- def join?
66
- false
67
- end
68
- end
69
103
  end
@@ -1,12 +1,10 @@
1
1
  module Arel
2
2
  class Lock < Compound
3
- attributes :relation, :locked
4
- deriving :initialize, :==
3
+ attr_reader :locked
5
4
 
6
- def initialize(relation, locked, &block)
7
- @relation = relation
8
- @locked = locked.blank? ? " FOR UPDATE" : locked
5
+ def initialize(relation, locked)
6
+ super(relation)
7
+ @locked = true == locked ? " FOR UPDATE" : locked
9
8
  end
10
9
  end
11
10
  end
12
-
@@ -1,13 +1,10 @@
1
1
  module Arel
2
2
  class Order < Compound
3
- attributes :relation, :orderings
4
- deriving :==
5
- requires :ordering
3
+ attr_reader :orderings
6
4
 
7
- def initialize(relation, *orderings, &block)
8
- @relation = relation
9
- @orderings = (orderings + arguments_from_block(relation, &block)) \
10
- .collect { |o| o.bind(relation) }
5
+ def initialize(relation, orderings)
6
+ super(relation)
7
+ @orderings = orderings.collect { |o| o.bind(relation) }
11
8
  end
12
9
 
13
10
  # TESTME
@@ -15,5 +12,12 @@ module Arel
15
12
  # QUESTION - do we still need relation.orders ?
16
13
  (orderings + relation.orders).collect { |o| o.bind(self) }.collect { |o| o.to_ordering }
17
14
  end
15
+
16
+ def eval
17
+ unoperated_rows.sort do |row1, row2|
18
+ ordering = orders.detect { |o| o.eval(row1, row2) != 0 } || orders.last
19
+ ordering.eval(row1, row2)
20
+ end
21
+ end
18
22
  end
19
23
  end
@@ -1,20 +1,20 @@
1
1
  module Arel
2
2
  class Project < Compound
3
- attributes :relation, :projections
4
- deriving :==
3
+ attr_reader :projections, :attributes, :christener
5
4
 
6
- def initialize(relation, *projections, &block)
7
- @relation = relation
8
- @projections = (projections + arguments_from_block(relation, &block)) \
9
- .collect { |p| p.bind(relation) }
10
- end
11
-
12
- def attributes
13
- @attributes ||= Header.new(projections).bind(self)
5
+ def initialize(relation, projections)
6
+ super(relation)
7
+ @projections = projections.map { |p| p.bind(relation) }
8
+ @christener = Sql::Christener.new
9
+ @attributes = Header.new(projections.map { |x| x.bind(self) })
14
10
  end
15
11
 
16
12
  def externalizable?
17
13
  attributes.any? { |a| a.respond_to?(:aggregation?) && a.aggregation? } || relation.externalizable?
18
14
  end
15
+
16
+ def eval
17
+ unoperated_rows.collect { |r| r.slice(*projections) }
18
+ end
19
19
  end
20
20
  end
@@ -1,7 +1,14 @@
1
1
  module Arel
2
2
  class Skip < Compound
3
- attributes :relation, :skipped
4
- deriving :initialize, :==
5
- requires :skipping
3
+ attr_reader :relation, :skipped
4
+
5
+ def initialize relation, skipped
6
+ super(relation)
7
+ @skipped = skipped
8
+ end
9
+
10
+ def eval
11
+ unoperated_rows[skipped..-1]
12
+ end
6
13
  end
7
14
  end
@@ -1,11 +1,18 @@
1
1
  module Arel
2
2
  class Take < Compound
3
- attributes :relation, :taken
4
- deriving :initialize, :==
5
- requires :limiting
3
+ attr_reader :taken
4
+
5
+ def initialize relation, taken
6
+ super(relation)
7
+ @taken = taken
8
+ end
6
9
 
7
10
  def externalizable?
8
11
  true
9
12
  end
13
+
14
+ def eval
15
+ unoperated_rows[0, taken]
16
+ end
10
17
  end
11
18
  end
@@ -1,17 +1,23 @@
1
1
  module Arel
2
2
  class Where < Compound
3
- attributes :relation, :predicates
4
- deriving :==
5
- requires :restricting
3
+ attr_reader :predicates
6
4
 
7
- def initialize(relation, *predicates, &block)
8
- predicates = [yield(relation)] + predicates if block_given?
5
+ def initialize(relation, predicates)
6
+ super(relation)
9
7
  @predicates = predicates.map { |p| p.bind(relation) }
10
- @relation = relation
8
+ @wheres = nil
11
9
  end
12
10
 
13
11
  def wheres
14
12
  @wheres ||= relation.wheres + predicates
15
13
  end
14
+
15
+ def eval
16
+ unoperated_rows.select { |row| predicates.all? { |p| p.eval(row) } }
17
+ end
18
+
19
+ def to_sql(formatter = nil)
20
+ compiler.select_sql
21
+ end
16
22
  end
17
23
  end
@@ -1,9 +1,17 @@
1
1
  module Arel
2
2
  module Relation
3
+ include Enumerable
4
+
5
+ @@connection_tables_primary_keys = {}
6
+
3
7
  attr_reader :count
4
8
 
5
9
  def session
6
- Session.new
10
+ Session.instance
11
+ end
12
+
13
+ def join?
14
+ false
7
15
  end
8
16
 
9
17
  def call
@@ -14,123 +22,184 @@ module Arel
14
22
  self
15
23
  end
16
24
 
17
- module Enumerable
18
- include ::Enumerable
25
+ def externalize
26
+ @externalized ||= externalizable?? Externalization.new(self) : self
27
+ end
28
+
29
+ def externalizable?
30
+ false
31
+ end
19
32
 
20
- def each(&block)
21
- session.read(self).each(&block)
33
+ def compiler
34
+ @compiler ||= begin
35
+ Arel::SqlCompiler.const_get("#{engine.adapter_name}Compiler").new(self)
36
+ rescue
37
+ Arel::SqlCompiler::GenericCompiler.new(self)
22
38
  end
39
+ end
40
+
41
+ def to_sql(formatter = nil)
42
+ sql = compiler.select_sql
43
+
44
+ return sql unless formatter
45
+ formatter.select sql, self
46
+ end
47
+
48
+ def christener
49
+ @christener ||= Sql::Christener.new
50
+ end
51
+
52
+ def inclusion_predicate_sql
53
+ "IN"
54
+ end
55
+
56
+ def exclusion_predicate_sql
57
+ "NOT IN"
58
+ end
23
59
 
24
- def first
25
- session.read(self).first
60
+ def primary_key
61
+ connection_id = engine.connection.object_id
62
+ if @@connection_tables_primary_keys[connection_id] && @@connection_tables_primary_keys[connection_id].has_key?(table.name)
63
+ @@connection_tables_primary_keys[connection_id][table.name]
64
+ else
65
+ @@connection_tables_primary_keys[connection_id] ||= {}
66
+ @@connection_tables_primary_keys[connection_id][table.name] = engine.connection.primary_key(table.name)
26
67
  end
27
68
  end
28
- include Enumerable
29
69
 
30
- module Operable
31
- def join(other_relation = nil, join_class = InnerJoin)
32
- case other_relation
33
- when String
34
- StringJoin.new(self, other_relation)
35
- when Relation
36
- JoinOperation.new(join_class, self, other_relation)
70
+ def select_clauses
71
+ attributes.map { |a|
72
+ case a
73
+ when Value
74
+ a.value
37
75
  else
38
- self
76
+ a.to_sql(Sql::SelectClause.new(self))
39
77
  end
40
- end
78
+ }
79
+ end
80
+
81
+ def from_clauses
82
+ sources.empty? ? table_sql : sources
83
+ end
84
+
85
+ def where_clauses
86
+ wheres.map { |w| w.value }
87
+ end
88
+
89
+ def group_clauses
90
+ groupings.collect { |g| g.to_sql(Sql::GroupClause.new(self)) }
91
+ end
41
92
 
42
- def outer_join(other_relation = nil)
43
- join(other_relation, OuterJoin)
93
+ def having_clauses
94
+ havings.collect { |g| g.to_sql(Sql::HavingClause.new(self)) }
95
+ end
96
+
97
+ def order_clauses
98
+ orders.collect { |o| o.to_sql(Sql::OrderClause.new(self)) }
99
+ end
100
+
101
+ def each
102
+ session.read(self).each { |e| yield e }
103
+ end
104
+
105
+ def join(other_relation = nil, join_class = InnerJoin)
106
+ case other_relation
107
+ when String
108
+ StringJoin.new(self, other_relation)
109
+ when Relation
110
+ JoinOperation.new(join_class, self, other_relation)
111
+ else
112
+ self
44
113
  end
114
+ end
45
115
 
46
- [:where, :project, :order, :take, :skip, :group, :from, :having].each do |operation_name|
47
- class_eval <<-OPERATION, __FILE__, __LINE__
48
- def #{operation_name}(*arguments, &block)
49
- arguments.all?(&:blank?) && !block_given?? self : #{operation_name.to_s.classify}.new(self, *arguments, &block)
116
+ def outer_join(other_relation = nil)
117
+ join(other_relation, OuterJoin)
118
+ end
119
+
120
+ %w{
121
+ having group order
122
+ }.each do |op|
123
+ class_eval <<-OPERATION, __FILE__, __LINE__
124
+ def #{op}(*args)
125
+ args.all? { |x| x.blank? } ? self : #{op.capitalize}.new(self, args)
50
126
  end
51
- OPERATION
52
- end
127
+ OPERATION
128
+ end
53
129
 
54
- def lock(locking = nil)
55
- Lock.new(self, locking)
56
- end
130
+ def project *args
131
+ args.empty? ? self : Project.new(self, args)
132
+ end
57
133
 
58
- def alias
59
- Alias.new(self)
60
- end
134
+ def where clause = nil
135
+ clause ? Where.new(self, Array(clause)) : self
136
+ end
61
137
 
62
- module Writable
63
- def insert(record)
64
- session.create Insert.new(self, record)
65
- end
138
+ def skip thing = nil
139
+ thing ? Skip.new(self, thing) : self
140
+ end
66
141
 
67
- def update(assignments)
68
- session.update Update.new(self, assignments)
69
- end
142
+ def take count
143
+ Take.new self, count
144
+ end
70
145
 
71
- def delete
72
- session.delete Deletion.new(self)
73
- end
74
- end
75
- include Writable
146
+ def from thing
147
+ From.new self, thing
148
+ end
76
149
 
77
- JoinOperation = Struct.new(:join_class, :relation1, :relation2) do
78
- def on(*predicates)
79
- join_class.new(relation1, relation2, *predicates)
80
- end
81
- end
150
+ def lock(locking = true)
151
+ Lock.new(self, locking)
82
152
  end
83
- include Operable
84
153
 
85
- module AttributeAccessable
86
- def [](index)
87
- attributes[index]
88
- end
154
+ def alias
155
+ Alias.new(self)
156
+ end
89
157
 
90
- def find_attribute_matching_name(name)
91
- attributes.detect { |a| a.named?(name) } || Attribute.new(self, name)
92
- end
158
+ def insert(record)
159
+ session.create Insert.new(self, record)
160
+ end
93
161
 
94
- def find_attribute_matching_attribute(attribute)
95
- matching_attributes(attribute).max do |a1, a2|
96
- (a1.original_attribute / attribute) <=> (a2.original_attribute / attribute)
97
- end
98
- end
162
+ def update(assignments)
163
+ session.update Update.new(self, assignments)
164
+ end
99
165
 
100
- def position_of(attribute)
101
- (@position_of ||= Hash.new do |h, attribute|
102
- h[attribute] = attributes.index(self[attribute])
103
- end)[attribute]
104
- end
166
+ def delete
167
+ session.delete Deletion.new(self)
168
+ end
105
169
 
106
- private
107
- def matching_attributes(attribute)
108
- (@matching_attributes ||= attributes.inject({}) do |hash, a|
109
- (hash[a.is_a?(Value) ? a.value : a.root] ||= []) << a
110
- hash
111
- end)[attribute.root] || []
170
+ JoinOperation = Struct.new(:join_class, :relation1, :relation2) do
171
+ def on(*predicates)
172
+ join_class.new(relation1, relation2, *predicates)
112
173
  end
174
+ end
113
175
 
114
- def has_attribute?(attribute)
115
- !matching_attributes(attribute).empty?
116
- end
176
+ def [](index)
177
+ attributes[index]
178
+ end
179
+
180
+ def find_attribute_matching_name(name)
181
+ attributes.detect { |a| a.named?(name) } || Attribute.new(self, name)
182
+ end
183
+
184
+ def position_of(attribute)
185
+ @position_of ||= {}
186
+
187
+ return @position_of[attribute] if @position_of.key? attribute
188
+
189
+ @position_of[attribute] = attributes.index(attributes[attribute])
117
190
  end
118
- include AttributeAccessable
119
-
120
- module DefaultOperations
121
- def attributes; Header.new end
122
- def projections; [] end
123
- def wheres; [] end
124
- def orders; [] end
125
- def inserts; [] end
126
- def groupings; [] end
127
- def havings; [] end
128
- def joins(formatter = nil); nil end # FIXME
129
- def taken; nil end
130
- def skipped; nil end
131
- def sources; [] end
132
- def locked; [] end
133
- end
134
- include DefaultOperations
191
+
192
+ def attributes; Header.new end
193
+ def projections; [] end
194
+ def wheres; [] end
195
+ def orders; [] end
196
+ def inserts; [] end
197
+ def groupings; [] end
198
+ def havings; [] end
199
+ def joins(formatter = nil); nil end # FIXME
200
+ def taken; nil end
201
+ def skipped; nil end
202
+ def sources; [] end
203
+ def locked; [] end
135
204
  end
136
205
  end