bud 0.0.8 → 0.1.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +4 -10
- data/bin/budplot +1 -2
- data/docs/cheat.md +2 -15
- data/examples/basics/paths.rb +7 -7
- data/lib/bud/aggs.rb +15 -19
- data/lib/bud/bud_meta.rb +165 -77
- data/lib/bud/bust/bust.rb +11 -4
- data/lib/bud/collections.rb +643 -280
- data/lib/bud/depanalysis.rb +50 -25
- data/lib/bud/executor/elements.rb +592 -0
- data/lib/bud/executor/group.rb +104 -0
- data/lib/bud/executor/join.rb +638 -0
- data/lib/bud/graphs.rb +12 -11
- data/lib/bud/joins.rb +2 -1
- data/lib/bud/meta_algebra.rb +5 -4
- data/lib/bud/metrics.rb +9 -3
- data/lib/bud/monkeypatch.rb +131 -23
- data/lib/bud/rebl.rb +41 -28
- data/lib/bud/rewrite.rb +112 -440
- data/lib/bud/server.rb +3 -2
- data/lib/bud/source.rb +109 -0
- data/lib/bud/state.rb +16 -9
- data/lib/bud/storage/dbm.rb +62 -16
- data/lib/bud/storage/zookeeper.rb +2 -2
- data/lib/bud/viz.rb +8 -4
- data/lib/bud/viz_util.rb +10 -9
- data/lib/bud.rb +413 -199
- metadata +40 -55
- data/examples/deploy/tokenring-ec2.rb +0 -26
- data/examples/deploy/tokenring-fork.rb +0 -15
- data/examples/deploy/tokenring-thread.rb +0 -15
- data/examples/deploy/tokenring.rb +0 -47
- data/lib/bud/deploy/deployer.rb +0 -67
- data/lib/bud/deploy/ec2deploy.rb +0 -199
- data/lib/bud/deploy/forkdeploy.rb +0 -90
- data/lib/bud/deploy/threaddeploy.rb +0 -38
- data/lib/bud/storage/tokyocabinet.rb +0 -190
- data/lib/bud/stratify.rb +0 -85
data/lib/bud/depanalysis.rb
CHANGED
@@ -5,41 +5,66 @@ class DepAnalysis #:nodoc: all
|
|
5
5
|
include Bud
|
6
6
|
|
7
7
|
state do
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
# Data inserted by client, usually from t_depends and t_provides
|
9
|
+
scratch :depends, [:lhs, :op, :body, :neg]
|
10
|
+
scratch :providing, [:pred, :input]
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
# Intermediate state
|
13
|
+
scratch :depends_clean, [:lhs, :body, :neg, :temporal]
|
14
|
+
|
15
|
+
scratch :depends_tc, [:lhs, :body, :via, :neg, :temporal]
|
16
|
+
scratch :cycle, [:pred, :via, :neg, :temporal]
|
17
|
+
scratch :underspecified, [:pred, :input]
|
18
|
+
scratch :source, [:pred]
|
19
|
+
scratch :sink, [:pred]
|
15
20
|
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
bloom :analysis do
|
23
|
+
depends_clean <= depends do |d|
|
24
|
+
is_temporal = (d.op.to_s =~ /<[\+\-\~]/)
|
25
|
+
[d.lhs, d.body, d.neg, is_temporal]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Compute the transitive closure of "depends_clean" to detect cycles in
|
29
|
+
# the deductive fragment of the program.
|
30
|
+
depends_tc <= depends_clean do |d|
|
31
|
+
[d.lhs, d.body, d.body, d.neg, d.temporal]
|
32
|
+
end
|
33
|
+
depends_tc <= (depends_clean * depends_tc).pairs(:body => :lhs) do |b, r|
|
34
|
+
[b.lhs, r.body, b.body, (b.neg or r.neg), (b.temporal or r.temporal)]
|
35
|
+
end
|
36
|
+
|
37
|
+
cycle <= depends_tc do |d|
|
38
|
+
if d.lhs == d.body
|
39
|
+
unless d.neg and !d.temporal
|
40
|
+
[d.lhs, d.via, d.neg, d.temporal]
|
22
41
|
end
|
23
42
|
end
|
43
|
+
end
|
24
44
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
45
|
+
source <= providing do |p|
|
46
|
+
if p.input and !depends_tc.map{|d| d.lhs}.include? p.pred
|
47
|
+
[p.pred]
|
29
48
|
end
|
49
|
+
end
|
30
50
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
51
|
+
sink <= providing do |p|
|
52
|
+
if !p.input and !depends_tc.map{|d| d.body}.include? p.pred
|
53
|
+
[p.pred]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
underspecified <= providing do |p|
|
58
|
+
if p.input
|
59
|
+
unless depends_tc.map{|d| d.body if d.lhs != d.body}.include? p.pred
|
60
|
+
[p.pred, true]
|
61
|
+
end
|
62
|
+
else
|
63
|
+
unless depends_tc.map{|d| d.lhs if d.lhs != d.body}.include? p.pred
|
64
|
+
[p.pred, false]
|
40
65
|
end
|
41
66
|
end
|
42
|
-
|
67
|
+
end
|
43
68
|
end
|
44
69
|
end
|
45
70
|
|
@@ -0,0 +1,592 @@
|
|
1
|
+
require "set"
|
2
|
+
require 'bud/collections'
|
3
|
+
ELEMENT_BUFSIZE = 1
|
4
|
+
|
5
|
+
module Bud
|
6
|
+
# Usage example:
|
7
|
+
# p = PushElement.new(:r) do |inp|
|
8
|
+
# puts "in block"
|
9
|
+
# [inp] if inp.class <= Numeric and inp%2 == 0
|
10
|
+
# end
|
11
|
+
|
12
|
+
# p.insert(1)
|
13
|
+
# p.insert(nil)
|
14
|
+
class PushElement < BudCollection
|
15
|
+
attr_accessor :elem_name
|
16
|
+
attr_accessor :rescan, :invalidated
|
17
|
+
attr_reader :arity, :inputs, :found_delta, :refcount, :wired_by, :outputs
|
18
|
+
|
19
|
+
def initialize(name_in, bud_instance, collection_name=nil, given_schema=nil, defer_schema=false, &blk)
|
20
|
+
super(name_in, bud_instance, given_schema, defer_schema)
|
21
|
+
@blk = blk
|
22
|
+
@outputs = []
|
23
|
+
@pendings = []
|
24
|
+
@deletes = []
|
25
|
+
@delete_keys = []
|
26
|
+
@wired_by = []
|
27
|
+
@elem_name = name_in
|
28
|
+
@found_delta = false
|
29
|
+
@refcount = 1
|
30
|
+
@each_index = 0
|
31
|
+
@collection_name = collection_name
|
32
|
+
@invalidated = true
|
33
|
+
@rescan = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def wiring?
|
37
|
+
@bud_instance.toplevel.done_wiring == false
|
38
|
+
end
|
39
|
+
|
40
|
+
def wirings
|
41
|
+
@wirings ||= @outputs + @pendings + @deletes + @delete_keys
|
42
|
+
end
|
43
|
+
|
44
|
+
public
|
45
|
+
def print_wiring(depth=0, accum = "")
|
46
|
+
depth.times {print " "}
|
47
|
+
puts "#{accum} #{(self.object_id*2).to_s(16)}: #{qualified_tabname} (#{self.class})"
|
48
|
+
|
49
|
+
[@outputs, @pendings, @deletes, @delete_keys].each do |kind|
|
50
|
+
case kind.object_id
|
51
|
+
when @outputs.object_id
|
52
|
+
next_accum = "=> "
|
53
|
+
when @pendings.object_id
|
54
|
+
next_accum = "+> "
|
55
|
+
when @deletes.object_id, @delete_keys.object_id
|
56
|
+
next_accum = "-> "
|
57
|
+
end
|
58
|
+
|
59
|
+
kind.each do |o|
|
60
|
+
if o.respond_to?(:print_wiring)
|
61
|
+
o.print_wiring(depth+1, next_accum)
|
62
|
+
else
|
63
|
+
(depth+1).times {print " "}
|
64
|
+
print "#{next_accum} "
|
65
|
+
if o.class <= Bud::BudCollection
|
66
|
+
puts "#{(o.object_id*2).to_s(16)}: #{o.qualified_tabname} (#{o.class})"
|
67
|
+
else
|
68
|
+
puts "#{(o.object_id*2).to_s(16)}: (#{o.class.name})"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def check_wiring
|
76
|
+
if @blk.nil? and @outputs.empty? and @pendings.empty? and @deletes.empty? and @delete_keys.empty?
|
77
|
+
raise "no output specified for PushElement #{@qualified_tabname}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_block(&blk)
|
82
|
+
@blk = blk
|
83
|
+
end
|
84
|
+
def wire_to(element)
|
85
|
+
unless element.methods.include? :insert or element.methods.include? "insert"
|
86
|
+
raise Bud::Error, "attempt to wire_to element without insert method"
|
87
|
+
end
|
88
|
+
# elem_name = element.respond_to?(:tabname) ? element.tabname : element.elem_name
|
89
|
+
# puts "wiring #{self.elem_name} to #{elem_name}"
|
90
|
+
@outputs << element
|
91
|
+
element.wired_by << self if element.respond_to? :wired_by
|
92
|
+
end
|
93
|
+
def wire_to_pending(element)
|
94
|
+
raise Bud::Error, "attempt to wire_to_pending element without pending_merge method" unless element.methods.include? "pending_merge" or element.methods.include? :pending_merge
|
95
|
+
elem_name = element.respond_to?(:tabname) ? element.tabname : element.elem_name
|
96
|
+
# puts "wiring #{self.elem_name} to #{elem_name}(pending)"
|
97
|
+
@pendings << element
|
98
|
+
element.wired_by << self if element.respond_to? :wired_by
|
99
|
+
end
|
100
|
+
def wire_to_delete(element)
|
101
|
+
raise Bud::Error, "attempt to wire_to_delete element without pending_delete method" unless element.methods.include? "pending_delete" or element.methods.include? :pending_delete
|
102
|
+
elem_name = element.respond_to?(:tabname) ? element.tabname : element.elem_name
|
103
|
+
# puts "wiring #{self.elem_name} to #{elem_name}(delete)"
|
104
|
+
@deletes << element
|
105
|
+
element.wired_by << self if element.respond_to? :wired_by
|
106
|
+
end
|
107
|
+
def wire_to_delete_by_key(element)
|
108
|
+
raise Bud::Error, "attempt to wire_to_delete_by_key element without pending_delete_keys method" unless element.methods.include? "pending_delete_keys" or element.methods.include? :pending_delete_keys
|
109
|
+
elem_name = element.respond_to?(:tabname) ? element.tabname : element.elem_name
|
110
|
+
# puts "wiring #{self.elem_name} to #{elem_name}(delete)"
|
111
|
+
@delete_keys << element
|
112
|
+
element.wired_by << self if element.respond_to? :wired_by
|
113
|
+
end
|
114
|
+
|
115
|
+
def rescan_at_tick
|
116
|
+
false
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def insert(item, source=nil)
|
121
|
+
push_out(item)
|
122
|
+
end
|
123
|
+
|
124
|
+
def tick
|
125
|
+
invalidate_cache if @invalidated
|
126
|
+
end
|
127
|
+
|
128
|
+
def tick_deltas
|
129
|
+
@found_delta = false
|
130
|
+
end
|
131
|
+
|
132
|
+
def push_out(item, do_block=true)
|
133
|
+
if item
|
134
|
+
blk = @blk if do_block
|
135
|
+
if blk
|
136
|
+
item = item.to_a if blk.arity > 1
|
137
|
+
begin
|
138
|
+
item = blk.call item
|
139
|
+
rescue Exception
|
140
|
+
raise
|
141
|
+
end
|
142
|
+
end
|
143
|
+
@outputs.each do |ou|
|
144
|
+
if ou.class <= Bud::PushElement
|
145
|
+
#the_name = ou.elem_name
|
146
|
+
# puts "#{self.object_id%10000} (#{elem_name}) -> #{ou.object_id%10000} (#{the_name}): #{item.inspect}"
|
147
|
+
ou.insert(item,self)
|
148
|
+
elsif ou.class <= Bud::BudCollection
|
149
|
+
# the_name = ou.tabname
|
150
|
+
# puts "#{self.object_id%10000} (#{elem_name}) -> #{ou.object_id%10000} (#{the_name}): #{item.inspect}"
|
151
|
+
ou.do_insert(item,ou.new_delta)
|
152
|
+
else
|
153
|
+
raise "Expected either a PushElement or a BudCollection"
|
154
|
+
end
|
155
|
+
end unless item.nil?
|
156
|
+
# for all the following, o is a BudCollection
|
157
|
+
@deletes.each{|o| o.pending_delete([item])} unless item.nil?
|
158
|
+
@delete_keys.each{|o| o.pending_delete_keys([item])} unless item.nil?
|
159
|
+
@pendings.each{|o| o.pending_merge([item])} unless item.nil?
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# default for stateless elements
|
165
|
+
public
|
166
|
+
def add_rescan_invalidate(rescan, invalidate)
|
167
|
+
# if any of the source elements are in rescan mode, then put this node in rescan.
|
168
|
+
srcs = non_temporal_predecessors
|
169
|
+
if srcs.any?{|p| rescan.member? p}
|
170
|
+
rescan << self
|
171
|
+
end
|
172
|
+
|
173
|
+
# pass the current state to the non-element outputs, and see if they end up marking this node for rescan
|
174
|
+
invalidate_tables(rescan, invalidate)
|
175
|
+
|
176
|
+
# finally, if this node is in rescan, pass the request on to all source elements
|
177
|
+
if rescan.member? self
|
178
|
+
srcs.each{|e| rescan << e} # propagate a rescan request to all sources.
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def invalidate_tables(rescan, invalidate)
|
183
|
+
# exchange rescan and invalidate information with tables. If this node is in rescan, it may invalidate a target
|
184
|
+
# table (if it is a scratch). And if the target node is invalidated, this node marks itself for rescan to
|
185
|
+
# enable a refill of that table at run-time
|
186
|
+
|
187
|
+
@outputs.each do |o|
|
188
|
+
unless o.class <= PushElement
|
189
|
+
o.add_rescan_invalidate(rescan, invalidate) unless o.class <= PushElement
|
190
|
+
rescan << self if invalidate.member? o
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def <<(i)
|
196
|
+
insert(i, nil)
|
197
|
+
end
|
198
|
+
public
|
199
|
+
def flush
|
200
|
+
end
|
201
|
+
|
202
|
+
def invalidate_cache
|
203
|
+
#override to get rid of cached information.
|
204
|
+
end
|
205
|
+
public
|
206
|
+
def stratum_end
|
207
|
+
end
|
208
|
+
#public
|
209
|
+
#def set_schema(schema)
|
210
|
+
# @schema=schema
|
211
|
+
# setup_accessors
|
212
|
+
#end
|
213
|
+
|
214
|
+
|
215
|
+
####
|
216
|
+
# and now, the Bloom-facing methods
|
217
|
+
public
|
218
|
+
def pro(the_name = @elem_name, the_schema = schema, &blk)
|
219
|
+
toplevel = @bud_instance.toplevel
|
220
|
+
elem = Bud::PushElement.new('project' + object_id.to_s, toplevel.this_rule_context, @collection_name, the_schema)
|
221
|
+
#elem.init_schema(the_schema) unless the_schema.nil?
|
222
|
+
self.wire_to(elem)
|
223
|
+
elem.set_block(&blk)
|
224
|
+
toplevel.push_elems[[self.object_id,:pro,blk]] = elem
|
225
|
+
return elem
|
226
|
+
end
|
227
|
+
|
228
|
+
alias each pro
|
229
|
+
|
230
|
+
public
|
231
|
+
def each_with_index(the_name = elem_name, the_schema = schema, &blk)
|
232
|
+
toplevel = @bud_instance.toplevel
|
233
|
+
elem = Bud::PushEachWithIndex.new('each_with_index' + object_id.to_s, toplevel.this_rule_context, @collection_name)
|
234
|
+
elem.set_block(&blk)
|
235
|
+
self.wire_to(elem)
|
236
|
+
toplevel.push_elems[[self.object_id,:each,blk]] = elem
|
237
|
+
end
|
238
|
+
|
239
|
+
def join(elem2, &blk)
|
240
|
+
# cached = @bud_instance.push_elems[[self.object_id,:join,[self,elem2], @bud_instance, blk]]
|
241
|
+
# if cached.nil?
|
242
|
+
elem2 = elem2.to_push_elem unless elem2.class <= PushElement
|
243
|
+
toplevel = @bud_instance.toplevel
|
244
|
+
join = Bud::PushSHJoin.new([self,elem2], toplevel.this_rule_context, [])
|
245
|
+
self.wire_to(join)
|
246
|
+
elem2.wire_to(join)
|
247
|
+
toplevel.push_elems[[self.object_id,:join,[self,elem2], toplevel, blk]] = join
|
248
|
+
toplevel.push_joins[toplevel.this_stratum] << join
|
249
|
+
# else
|
250
|
+
# cached.refcount += 1
|
251
|
+
# end
|
252
|
+
return toplevel.push_elems[[self.object_id,:join,[self,elem2], toplevel, blk]]
|
253
|
+
end
|
254
|
+
def *(elem2, &blk)
|
255
|
+
join(elem2, &blk)
|
256
|
+
end
|
257
|
+
|
258
|
+
def notin(elem2, preds=nil, &blk)
|
259
|
+
toplevel = @bud_instance.toplevel
|
260
|
+
notin_elem = Bud::PushNotIn.new([self, elem2], toplevel.this_rule_context, preds, &blk)
|
261
|
+
self.wire_to(notin_elem)
|
262
|
+
elem2.wire_to(notin_elem)
|
263
|
+
toplevel.push_elems[[self.object_id, :notin, collection, toplevel, blk]] == notin_elem
|
264
|
+
return notin_elem
|
265
|
+
end
|
266
|
+
|
267
|
+
def merge(source)
|
268
|
+
if source.class <= PushElement and wiring?
|
269
|
+
source.wire_to(self)
|
270
|
+
else
|
271
|
+
source.each{|i| self << i}
|
272
|
+
end
|
273
|
+
end
|
274
|
+
alias <= merge
|
275
|
+
superator "<~" do |o|
|
276
|
+
raise Bud::Error, "Illegal use of <~ with pusher '#{tabname}' on left"
|
277
|
+
end
|
278
|
+
|
279
|
+
superator "<-" do |o|
|
280
|
+
raise Bud::Error, "Illegal use of <- with pusher '#{tabname}' on left"
|
281
|
+
end
|
282
|
+
|
283
|
+
superator "<+" do |o|
|
284
|
+
raise Bud::Error, "Illegal use of <+ with pusher '#{tabname}' on left"
|
285
|
+
end
|
286
|
+
|
287
|
+
def group(keycols, *aggpairs, &blk)
|
288
|
+
# establish schema
|
289
|
+
keycols = [] if keycols.nil?
|
290
|
+
keycols = keycols.map{|c| canonicalize_col(c)}
|
291
|
+
keynames = keycols.map{|k| k[2]}
|
292
|
+
aggcolsdups = aggpairs.map{|ap| ap[0].class.name.split("::").last}
|
293
|
+
aggcols = []
|
294
|
+
aggcolsdups.each_with_index do |n, i|
|
295
|
+
aggcols << "#{n.downcase}_#{i}".to_sym
|
296
|
+
end
|
297
|
+
if aggcols.empty?
|
298
|
+
the_schema = keynames
|
299
|
+
else
|
300
|
+
the_schema = { keynames => aggcols }
|
301
|
+
end
|
302
|
+
|
303
|
+
aggpairs = aggpairs.map{|ap| ap[1].nil? ? [ap[0]] : [ap[0], canonicalize_col(ap[1])]}
|
304
|
+
toplevel = @bud_instance.toplevel
|
305
|
+
# if @bud_instance.push_elems[[self.object_id, :group, keycols, aggpairs, blk]].nil?
|
306
|
+
g = Bud::PushGroup.new('grp'+Time.new.tv_usec.to_s, toplevel.this_rule_context, @collection_name, keycols, aggpairs, the_schema, &blk)
|
307
|
+
self.wire_to(g)
|
308
|
+
toplevel.push_elems[[self.object_id, :group, keycols, aggpairs, blk]] = g
|
309
|
+
# end
|
310
|
+
# toplevel.push_elems[[self.object_id, :group, keycols, aggpairs, blk]]
|
311
|
+
return g
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
def argagg(aggname, gbkey_cols, collection, &blk)
|
316
|
+
gbkey_cols = gbkey_cols.map{|c| canonicalize_col(c)}
|
317
|
+
collection = canonicalize_col(collection)
|
318
|
+
toplevel = @bud_instance.toplevel
|
319
|
+
agg = toplevel.send(aggname, collection)[0]
|
320
|
+
raise Bud::Error, "#{aggname} not declared exemplary" unless agg.class <= Bud::ArgExemplary
|
321
|
+
keynames = gbkey_cols.map do |k|
|
322
|
+
if k.class == Symbol
|
323
|
+
k.to_s
|
324
|
+
else
|
325
|
+
k[2]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
aggpairs = [[agg,collection]]
|
329
|
+
# if toplevel.push_elems[[self.object_id,:argagg, gbkey_cols, aggpairs, blk]].nil?
|
330
|
+
aa = Bud::PushArgAgg.new('argagg'+Time.new.tv_usec.to_s, toplevel.this_rule_context, @collection_name, gbkey_cols, aggpairs, schema, &blk)
|
331
|
+
self.wire_to(aa)
|
332
|
+
toplevel.push_elems[[self.object_id,:argagg, gbkey_cols, aggpairs, blk]] = aa
|
333
|
+
# end
|
334
|
+
# return toplevel.push_elems[[self.object_id,:argagg, gbkey_cols, aggpairs, blk]]
|
335
|
+
return aa
|
336
|
+
end
|
337
|
+
def argmax(gbcols, col, &blk)
|
338
|
+
argagg(gbcols, Bud::max(col), blk)
|
339
|
+
end
|
340
|
+
def argmin(gbcols, col, &blk)
|
341
|
+
argagg(gbcols, Bud::min(col), blk)
|
342
|
+
end
|
343
|
+
def sort(name=nil, bud_instance=nil, the_schema=nil, &blk)
|
344
|
+
elem = Bud::PushSort.new(name, bud_instance, the_schema, &blk)
|
345
|
+
wire_to(elem)
|
346
|
+
elem
|
347
|
+
end
|
348
|
+
def push_predicate(pred_symbol, name=nil, bud_instance=nil, the_schema=nil, &blk)
|
349
|
+
elem = Bud::PushPredicate.new(pred_symbol, name, bud_instance, the_schema, &blk)
|
350
|
+
wire_to(elem)
|
351
|
+
elem
|
352
|
+
end
|
353
|
+
def all?(name=nil, bud_instance=nil, the_schema=nil, &blk)
|
354
|
+
push_predicate(:all?, name, bud_instance, the_schema, &blk)
|
355
|
+
end
|
356
|
+
def any?(name=nil, bud_instance=nil, the_schema=nil, &blk)
|
357
|
+
push_predicate(:any?, name, bud_instance, the_schema, &blk)
|
358
|
+
end
|
359
|
+
def include?(name=nil, bud_instance=nil, the_schema=nil, &blk)
|
360
|
+
push_predicate(:include?, name, bud_instance, the_schema, &blk)
|
361
|
+
end
|
362
|
+
def member?(name=nil, bud_instance=nil, the_schema=nil, &blk)
|
363
|
+
push_predicate(:member?, name, bud_instance, the_schema, &blk)
|
364
|
+
end
|
365
|
+
def none?(name=nil, bud_instance=nil, the_schema=nil, &blk)
|
366
|
+
push_predicate(:none?, name, bud_instance, the_schema, &blk)
|
367
|
+
end
|
368
|
+
def one?(name=nil, bud_instance=nil, the_schema=nil, &blk)
|
369
|
+
push_predicate(:one?, name, bud_instance, the_schema, &blk)
|
370
|
+
end
|
371
|
+
|
372
|
+
def reduce(initial, &blk)
|
373
|
+
@memo = initial
|
374
|
+
retval = Bud::PushReduce.new('reduce'+Time.new.tv_usec.to_s, @bud_instance, @collection_name, schema, initial, &blk)
|
375
|
+
self.wire_to(retval)
|
376
|
+
retval
|
377
|
+
end
|
378
|
+
|
379
|
+
alias on_exists? pro
|
380
|
+
def on_include?(item, &blk)
|
381
|
+
toplevel = @bud_instance.toplevel
|
382
|
+
if toplevel.push_elems[[self.object_id,:on_include?, item, blk]].nil?
|
383
|
+
inc = pro{|i| blk.call(item) if i == item and not blk.nil?}
|
384
|
+
wire_to(inc)
|
385
|
+
toplevel.push_elems[[self.object_id,:on_include?, item, blk]] = inc
|
386
|
+
end
|
387
|
+
toplevel.push_elems[[self.object_id,:on_include?, item, blk]]
|
388
|
+
end
|
389
|
+
def inspected
|
390
|
+
toplevel = @bud_instance.toplevel
|
391
|
+
if toplevel.push_elems[[self.object_id,:inspected]].nil?
|
392
|
+
ins = pro{|i| [i.inspect]}
|
393
|
+
self.wire_to(ins)
|
394
|
+
toplevel.push_elems[[self.object_id,:inspected]] = ins
|
395
|
+
end
|
396
|
+
toplevel.push_elems[[self.object_id,:inspected]]
|
397
|
+
end
|
398
|
+
|
399
|
+
def to_enum
|
400
|
+
# scr = @bud_instance.scratch(("scratch_" + Process.pid.to_s + "_" + object_id.to_s + "_" + rand(10000).to_s).to_sym, schema)
|
401
|
+
scr = []
|
402
|
+
self.wire_to(scr)
|
403
|
+
scr
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
class PushStatefulElement < PushElement
|
408
|
+
|
409
|
+
def rescan_at_tick
|
410
|
+
true
|
411
|
+
end
|
412
|
+
|
413
|
+
def rescan
|
414
|
+
true # always gives an entire dump of its contents
|
415
|
+
end
|
416
|
+
|
417
|
+
def add_rescan_invalidate(rescan, invalidate)
|
418
|
+
# If an upstream node is set to rescan, a stateful node invalidates its cache
|
419
|
+
# In addition, a stateful node always rescans its own contents (doesn't need to pass a rescan request to its
|
420
|
+
# its source nodes
|
421
|
+
|
422
|
+
rescan << self
|
423
|
+
srcs = non_temporal_predecessors
|
424
|
+
if srcs.any? {|p| rescan.member? p}
|
425
|
+
invalidate << self
|
426
|
+
end
|
427
|
+
|
428
|
+
invalidate_tables(rescan, invalidate)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
class PushPredicate < PushStatefulElement
|
433
|
+
def initialize(pred_symbol, elem_name=nil, collection_name=nil, bud_instance=nil, schema_in=nil, &blk)
|
434
|
+
@pred_symbol = pred_symbol
|
435
|
+
@in_buf = []
|
436
|
+
super(elem_name, bud_instance, collection_name, schema_in, &blk)
|
437
|
+
end
|
438
|
+
|
439
|
+
def insert(item, source)
|
440
|
+
@in_buf << item
|
441
|
+
end
|
442
|
+
|
443
|
+
public
|
444
|
+
def flush
|
445
|
+
# always rescans
|
446
|
+
@in_buf.send(@pred_symbol, @blk)
|
447
|
+
end
|
448
|
+
|
449
|
+
def invalidate_cache
|
450
|
+
@in_buf.clear
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
class PushSort < PushStatefulElement
|
455
|
+
def initialize(elem_name=nil, bud_instance=nil, collection_name=nil, schema_in=nil, &blk)
|
456
|
+
@sortbuf = []
|
457
|
+
super(elem_name, bud_instance, collection_name, schema_in, &blk)
|
458
|
+
end
|
459
|
+
|
460
|
+
def insert(item, source)
|
461
|
+
@sortbuf << item
|
462
|
+
end
|
463
|
+
|
464
|
+
def flush
|
465
|
+
unless @sortbuf.empty?
|
466
|
+
@sortbuf.sort!(&@blk)
|
467
|
+
@sortbuf.each do |t|
|
468
|
+
push_out(t, false)
|
469
|
+
end
|
470
|
+
@sortbuf = []
|
471
|
+
end
|
472
|
+
nil
|
473
|
+
end
|
474
|
+
|
475
|
+
def invalidate_cache
|
476
|
+
@sortbuf = []
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
class ScannerElement < PushElement
|
481
|
+
attr_reader :collection
|
482
|
+
attr_reader :rescan_set, :invalidate_set
|
483
|
+
def initialize(elem_name, bud_instance, collection_in, the_schema=collection_in.schema, &blk)
|
484
|
+
# puts self.class
|
485
|
+
super(elem_name, bud_instance, collection_in.qualified_tabname, the_schema)
|
486
|
+
@collection = collection_in
|
487
|
+
@rescan_set = []
|
488
|
+
@invalidate_set = []
|
489
|
+
end
|
490
|
+
|
491
|
+
def rescan
|
492
|
+
@rescan || @collection.invalidated
|
493
|
+
end
|
494
|
+
|
495
|
+
def rescan_at_tick
|
496
|
+
@collection.invalidate_at_tick # need to scan afresh if collection invalidated.
|
497
|
+
end
|
498
|
+
|
499
|
+
def invalidate_at_tick(rescan, invalidate)
|
500
|
+
# collection of others to rescan/invalidate if this scanner's collection were to be invalidated.
|
501
|
+
@rescan_set = rescan
|
502
|
+
@invalidate_set = invalidate
|
503
|
+
end
|
504
|
+
|
505
|
+
public
|
506
|
+
def add_rescan_invalidate(rescan, invalidate)
|
507
|
+
# scanner elements are never directly connected to tables.
|
508
|
+
rescan << self if invalidate.member? @collection
|
509
|
+
|
510
|
+
# Note also that this node can be nominated for rescan by a target node; in other words, a scanner element
|
511
|
+
# can be set to rescan even if the collection is not invalidated.
|
512
|
+
end
|
513
|
+
|
514
|
+
def scan(first_iter)
|
515
|
+
if (first_iter)
|
516
|
+
if rescan
|
517
|
+
# scan entire storage
|
518
|
+
@collection.each_raw {|item|
|
519
|
+
push_out(item)
|
520
|
+
}
|
521
|
+
else
|
522
|
+
# In the first iteration, tick_delta would be non-null IFF the collection has grown in an earlier stratum
|
523
|
+
@collection.tick_delta.each {|item| push_out(item)}
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
# send deltas out in all cases
|
528
|
+
@collection.delta.each_value {|item| push_out(item)}
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
class PushReduce < PushStatefulElement
|
533
|
+
def initialize(elem_name, bud_instance, collection_name, schema_in, initial, &blk)
|
534
|
+
@memo = initial
|
535
|
+
@blk = blk
|
536
|
+
super(elem_name, bud_instance, collection_name, schema)
|
537
|
+
end
|
538
|
+
|
539
|
+
def insert(i, source=nil)
|
540
|
+
@memo = @blk.call(@memo,i)
|
541
|
+
end
|
542
|
+
|
543
|
+
def invalidate_cache
|
544
|
+
@memo.clear
|
545
|
+
end
|
546
|
+
|
547
|
+
public
|
548
|
+
def flush
|
549
|
+
@memo.each do |k,v|
|
550
|
+
push_out([k,v], false)
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
class PushEachWithIndex < PushStatefulElement
|
556
|
+
def initialize(elem_name, bud_instance, collection_name)
|
557
|
+
super(elem_name, bud_instance, collection_name)
|
558
|
+
@each_index = 0
|
559
|
+
end
|
560
|
+
|
561
|
+
def add_rescan_invalidate(rescan, invalidate)
|
562
|
+
srcs = non_temporal_predecessors
|
563
|
+
if srcs.any? {|p| rescan.member? p}
|
564
|
+
invalidate << self
|
565
|
+
rescan << self
|
566
|
+
end
|
567
|
+
|
568
|
+
invalidate_tables(rescan, invalidate)
|
569
|
+
|
570
|
+
# This node has some state (@each_index), but not the tuples. If it is in rescan mode, then it must ask its
|
571
|
+
# sources to rescan, and restart its index.
|
572
|
+
if rescan.member? self
|
573
|
+
invalidate << self
|
574
|
+
srcs.each {|e| rescan << e}
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
def invalidate_cache
|
579
|
+
@each_index = 0
|
580
|
+
end
|
581
|
+
|
582
|
+
def stratum_end
|
583
|
+
@each_index = 0
|
584
|
+
end
|
585
|
+
|
586
|
+
def insert(item, source=nil)
|
587
|
+
ix = @each_index
|
588
|
+
@each_index = ix + 1
|
589
|
+
push_out([item, ix])
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|