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/collections.rb
CHANGED
@@ -1,28 +1,37 @@
|
|
1
1
|
require 'msgpack'
|
2
2
|
|
3
|
+
$struct_classes = {}
|
4
|
+
$EMPTY_HASH = {}
|
3
5
|
module Bud
|
4
6
|
########
|
5
7
|
#--
|
6
|
-
# the collection types
|
8
|
+
# the collection types ``
|
7
9
|
# each collection is partitioned into 4:
|
8
10
|
# - pending holds tuples deferred til the next tick
|
9
11
|
# - storage holds the "normal" tuples
|
10
12
|
# - delta holds the delta for rhs's of rules during semi-naive
|
11
13
|
# - new_delta will hold the lhs tuples currently being produced during s-n
|
14
|
+
# - tick_delta holds \Union(delta_i) for each delta_i processed in fixpoint iteration i.
|
12
15
|
#++
|
13
16
|
|
14
17
|
class BudCollection
|
15
18
|
include Enumerable
|
16
19
|
|
17
|
-
|
18
|
-
#
|
19
|
-
|
20
|
-
attr_reader :
|
21
|
-
|
20
|
+
attr_accessor :bud_instance, :locspec_idx, :tabname # :nodoc: all
|
21
|
+
attr_reader :cols, :key_cols # :nodoc: all
|
22
|
+
attr_reader :struct
|
23
|
+
attr_reader :storage, :delta, :new_delta, :pending, :tick_delta # :nodoc: all
|
24
|
+
attr_accessor :qualified_tabname
|
25
|
+
attr_accessor :invalidated, :to_delete, :rescan
|
26
|
+
attr_accessor :is_source
|
27
|
+
attr_accessor :wired_by
|
22
28
|
|
23
29
|
def initialize(name, bud_instance, given_schema=nil, defer_schema=false) # :nodoc: all
|
24
30
|
@tabname = name
|
25
31
|
@bud_instance = bud_instance
|
32
|
+
@invalidated = true
|
33
|
+
@is_source = true # unless it shows up on the lhs of some rule
|
34
|
+
@wired_by = []
|
26
35
|
init_schema(given_schema) unless given_schema.nil? and defer_schema
|
27
36
|
init_buffers
|
28
37
|
end
|
@@ -34,31 +43,42 @@ module Bud
|
|
34
43
|
init_deltas
|
35
44
|
end
|
36
45
|
|
37
|
-
|
46
|
+
public
|
38
47
|
def init_schema(given_schema)
|
39
48
|
given_schema ||= {[:key]=>[:val]}
|
40
49
|
|
50
|
+
|
51
|
+
@given_schema = given_schema
|
52
|
+
@cols, @key_cols = BudCollection.parse_schema(given_schema)
|
41
53
|
# Check that no location specifiers appear in the schema. In the case of
|
42
54
|
# channels, the location specifier has already been stripped from the
|
43
55
|
# user-specified schema.
|
44
|
-
|
56
|
+
@cols.each do |s|
|
45
57
|
if s.to_s.start_with? "@"
|
46
58
|
raise Bud::Error, "illegal use of location specifier (@) in column #{s} of non-channel collection #{tabname}"
|
47
59
|
end
|
48
60
|
end
|
49
61
|
|
50
|
-
@
|
51
|
-
|
62
|
+
if @cols.size == 0
|
63
|
+
@cols = nil
|
64
|
+
else
|
65
|
+
@struct = ($struct_classes[@cols] ||= Struct.new(*@cols))
|
66
|
+
@structlen = @struct.members.length
|
67
|
+
end
|
52
68
|
@key_colnums = key_cols.map {|k| @cols.index(k)}
|
53
69
|
setup_accessors
|
54
70
|
end
|
55
71
|
|
72
|
+
def qualified_tabname
|
73
|
+
@qualified_tabname ||= @bud_instance.toplevel? ? tabname : (@bud_instance.qualified_name + "." + tabname.to_s).to_sym
|
74
|
+
end
|
75
|
+
|
56
76
|
# The user-specified schema might come in two forms: a hash of Array =>
|
57
77
|
# Array (key_cols => remaining columns), or simply an Array of columns (if
|
58
78
|
# no key_cols were specified). Return a pair: [list of (all) columns, list
|
59
79
|
# of key columns]
|
60
80
|
private
|
61
|
-
def parse_schema(given_schema)
|
81
|
+
def self.parse_schema(given_schema)
|
62
82
|
if given_schema.respond_to? :keys
|
63
83
|
raise Bud::Error, "invalid schema for #{tabname}" if given_schema.length != 1
|
64
84
|
key_cols = given_schema.keys.first
|
@@ -71,16 +91,20 @@ module Bud
|
|
71
91
|
cols = key_cols + val_cols
|
72
92
|
cols.each do |c|
|
73
93
|
if c.class != Symbol
|
74
|
-
raise Bud::Error, "
|
94
|
+
raise Bud::Error, "Invalid column name \"#{c}\", type \"#{c.class}\""
|
75
95
|
end
|
76
96
|
end
|
77
97
|
if cols.uniq.length < cols.length
|
78
|
-
raise Bud::Error, "schema
|
98
|
+
raise Bud::Error, "schema #{given_schema.inspect} contains duplicate names"
|
79
99
|
end
|
80
100
|
|
81
101
|
return [cols, key_cols]
|
82
102
|
end
|
83
103
|
|
104
|
+
def inspect
|
105
|
+
"#{self.class}:#{self.object_id.to_s(16)} [#{qualified_tabname}]"
|
106
|
+
end
|
107
|
+
|
84
108
|
public
|
85
109
|
def clone_empty #:nodoc: all
|
86
110
|
self.class.new(tabname, bud_instance, @given_schema)
|
@@ -105,28 +129,27 @@ module Bud
|
|
105
129
|
# j = join link, path, {link.to => path.from}
|
106
130
|
private
|
107
131
|
def setup_accessors
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
(reserved == "method" and method(colname).arity == -1 and (eval(colname))[0] == self.tabname))
|
132
|
+
sc = @cols
|
133
|
+
return if sc.nil?
|
134
|
+
sc.each do |colname|
|
135
|
+
if name_reserved? colname
|
113
136
|
raise Bud::Error, "symbol :#{colname} reserved, cannot be used as column name for #{tabname}"
|
114
137
|
end
|
115
138
|
end
|
116
139
|
|
117
140
|
# set up schema accessors, which are class methods
|
118
|
-
|
119
|
-
|
120
|
-
define_method c do
|
141
|
+
@cols_access = Module.new do
|
142
|
+
sc.each_with_index do |c, i|
|
143
|
+
m = define_method c do
|
121
144
|
[@tabname, i, c]
|
122
145
|
end
|
123
146
|
end
|
124
147
|
end
|
125
|
-
self.extend
|
148
|
+
self.extend @cols_access
|
126
149
|
|
127
150
|
# now set up a Module for tuple accessors, which are instance methods
|
128
151
|
@tupaccess = Module.new do
|
129
|
-
|
152
|
+
sc.each_with_index do |colname, offset|
|
130
153
|
define_method colname do
|
131
154
|
self[offset]
|
132
155
|
end
|
@@ -134,51 +157,120 @@ module Bud
|
|
134
157
|
end
|
135
158
|
end
|
136
159
|
|
137
|
-
|
160
|
+
|
138
161
|
private
|
162
|
+
def name_reserved?(colname)
|
163
|
+
reserved = eval "defined?(#{colname})"
|
164
|
+
return false if reserved.nil?
|
165
|
+
if reserved == "method" and (method(colname).arity == 0 or method(colname).arity == -1)
|
166
|
+
begin
|
167
|
+
ret = eval("#{colname}")
|
168
|
+
if ret.kind_of? Array and ret.size == 3 and ret[0] == tabname
|
169
|
+
return false # schema redefinition (see tupaccess above), so name is not considered reserved
|
170
|
+
end
|
171
|
+
rescue # in case calling method throws an error
|
172
|
+
end
|
173
|
+
end
|
174
|
+
return true
|
175
|
+
end
|
176
|
+
|
177
|
+
# define methods to access tuple attributes by column name
|
178
|
+
public
|
139
179
|
def tuple_accessors(tup)
|
140
|
-
tup.
|
180
|
+
tup # XXX remove tuple_acessors everywhere.
|
141
181
|
end
|
142
182
|
|
143
183
|
# generate a tuple with the schema of this collection and nil values in each attribute
|
144
184
|
public
|
145
185
|
def null_tuple
|
146
|
-
|
186
|
+
@struct.new
|
147
187
|
end
|
148
188
|
|
149
189
|
# project the collection to its key attributes
|
150
190
|
public
|
151
191
|
def keys
|
152
|
-
self.
|
192
|
+
self.pro{|t| @key_colnums.map {|i| t[i]}}
|
153
193
|
end
|
154
194
|
|
155
195
|
# project the collection to its non-key attributes
|
156
196
|
public
|
157
197
|
def values
|
158
|
-
self.
|
198
|
+
self.pro{|t| (self.key_cols.length..self.cols.length-1).map{|i| t[i]}}
|
159
199
|
end
|
160
200
|
|
161
201
|
# map each item in the collection into a string, suitable for placement in stdio
|
162
202
|
public
|
163
203
|
def inspected
|
164
|
-
|
204
|
+
self.pro{|t| [t.inspect]}
|
205
|
+
# how about when this is called outside wiring?
|
206
|
+
# [["#{@tabname}: [#{self.map{|t| "\n (#{t.map{|v| v.inspect}.join ", "})"}}]"]]
|
207
|
+
end
|
208
|
+
|
209
|
+
# projection
|
210
|
+
public
|
211
|
+
def pro(the_name = tabname, the_schema = schema, &blk)
|
212
|
+
pusher = to_push_elem(the_name, the_schema)
|
213
|
+
pusher_pro = pusher.pro(&blk)
|
214
|
+
pusher_pro.elem_name = the_name
|
215
|
+
pusher_pro.tabname = the_name
|
216
|
+
pusher_pro
|
165
217
|
end
|
166
218
|
|
167
|
-
# akin to map, but modified for efficiency in Bloom statements
|
168
219
|
public
|
169
|
-
def
|
170
|
-
|
171
|
-
|
220
|
+
def each_with_index(the_name = tabname, the_schema = schema, &blk)
|
221
|
+
toplevel = @bud_instance.toplevel
|
222
|
+
if not toplevel.done_wiring
|
223
|
+
proj = pro(the_name, the_schema)
|
224
|
+
elem = Bud::PushEachWithIndex.new('each_with_index' + object_id.to_s, toplevel.this_rule_context, tabname)
|
225
|
+
elem.set_block(&blk)
|
226
|
+
proj.wire_to(elem)
|
227
|
+
toplevel.push_elems[[self.object_id,:each,blk]] = elem
|
228
|
+
elem
|
172
229
|
else
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
230
|
+
storage.each_with_index
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
# ruby 1.9 defines flat_map to return "a new array with the concatenated results of running
|
236
|
+
# <em>block</em> once for every element". So we wire the input to a pro(&blk), and wire the output
|
237
|
+
# of that pro to a group that does accum.
|
238
|
+
public
|
239
|
+
def flat_map(&blk)
|
240
|
+
pusher = self.pro(&blk)
|
241
|
+
toplevel = @bud_instance.toplevel
|
242
|
+
elem = Bud::PushElement.new(tabname, toplevel.this_rule_context, tabname)
|
243
|
+
pusher.wire_to(elem)
|
244
|
+
f = Proc.new do |t|
|
245
|
+
t.each do |i|
|
246
|
+
elem.push_out(i,false)
|
177
247
|
end
|
178
|
-
|
248
|
+
nil
|
179
249
|
end
|
250
|
+
elem.set_block(&f)
|
251
|
+
toplevel.push_elems[[self.object_id,:flatten]] = elem
|
252
|
+
return elem
|
253
|
+
end
|
254
|
+
|
255
|
+
public
|
256
|
+
def sort(&blk)
|
257
|
+
pusher = self.pro
|
258
|
+
pusher.sort(@name, @bud_instance, @cols, &blk)
|
180
259
|
end
|
181
260
|
|
261
|
+
def rename(the_name, the_schema=nil)
|
262
|
+
# a scratch with this name should have been defined during rewriting
|
263
|
+
raise(Bud::Error, "rename failed to define a scratch named #{the_name}") unless @bud_instance.respond_to? the_name
|
264
|
+
retval = pro(the_name, the_schema)
|
265
|
+
#retval.init_schema(the_schema)
|
266
|
+
retval
|
267
|
+
end
|
268
|
+
|
269
|
+
# def to_enum
|
270
|
+
# pusher = self.pro
|
271
|
+
# pusher.to_enum
|
272
|
+
# end
|
273
|
+
|
182
274
|
# By default, all tuples in any rhs are in storage or delta. Tuples in
|
183
275
|
# new_delta will get transitioned to delta in the next iteration of the
|
184
276
|
# evaluator (but within the current time tick).
|
@@ -187,6 +279,21 @@ module Bud
|
|
187
279
|
each_from([@storage, @delta], &block)
|
188
280
|
end
|
189
281
|
|
282
|
+
public
|
283
|
+
def each_raw(&block)
|
284
|
+
@storage.each_value(&block)
|
285
|
+
end
|
286
|
+
|
287
|
+
public
|
288
|
+
def invalidate_at_tick
|
289
|
+
true # being conservative here as a default.
|
290
|
+
end
|
291
|
+
|
292
|
+
public
|
293
|
+
def non_temporal_predecessors
|
294
|
+
@wired_by.map {|elem| elem if elem.outputs.include? self}
|
295
|
+
end
|
296
|
+
|
190
297
|
public
|
191
298
|
def tick_metrics
|
192
299
|
strat_num = bud_instance.this_stratum
|
@@ -195,8 +302,8 @@ module Bud
|
|
195
302
|
addr = bud_instance.ip_port unless bud_instance.port.nil?
|
196
303
|
rule_txt = nil
|
197
304
|
bud_instance.metrics[:collections] ||= {}
|
198
|
-
bud_instance.metrics[:collections][{:addr=>addr, :tabname=>
|
199
|
-
bud_instance.metrics[:collections][{:addr=>addr, :tabname=>
|
305
|
+
bud_instance.metrics[:collections][{:addr=>addr, :tabname=>qualified_tabname, :strat_num=>strat_num, :rule_num=>rule_num}] ||= 0
|
306
|
+
bud_instance.metrics[:collections][{:addr=>addr, :tabname=>qualified_tabname, :strat_num=>strat_num, :rule_num=>rule_num}] += 1
|
200
307
|
end
|
201
308
|
|
202
309
|
private
|
@@ -204,7 +311,7 @@ module Bud
|
|
204
311
|
bufs.each do |b|
|
205
312
|
b.each_value do |v|
|
206
313
|
tick_metrics if bud_instance and bud_instance.options[:metrics]
|
207
|
-
yield v
|
314
|
+
yield tuple_accessors(v)
|
208
315
|
end
|
209
316
|
end
|
210
317
|
end
|
@@ -236,6 +343,7 @@ module Bud
|
|
236
343
|
def init_deltas
|
237
344
|
@delta = {}
|
238
345
|
@new_delta = {}
|
346
|
+
@tick_delta = []
|
239
347
|
end
|
240
348
|
|
241
349
|
public
|
@@ -257,18 +365,26 @@ module Bud
|
|
257
365
|
# is this enforced in do_insert?
|
258
366
|
check_enumerable(k)
|
259
367
|
t = @storage[k]
|
260
|
-
return t.nil? ? @delta[k] : t
|
368
|
+
return t.nil? ? @delta[k] : tuple_accessors(t)
|
261
369
|
end
|
262
370
|
|
263
371
|
# checks for +item+ in the collection
|
264
372
|
public
|
265
373
|
def include?(item)
|
266
374
|
return true if key_cols.nil? or (key_cols.empty? and length > 0)
|
267
|
-
return false if item.nil?
|
375
|
+
return false if item.nil?
|
268
376
|
key = get_key_vals(item)
|
269
377
|
return (item == self[key])
|
270
378
|
end
|
271
379
|
|
380
|
+
def length
|
381
|
+
@storage.length + @delta.length
|
382
|
+
end
|
383
|
+
|
384
|
+
def empty?
|
385
|
+
length == 0
|
386
|
+
end
|
387
|
+
|
272
388
|
# checks for an item for which +block+ produces a match
|
273
389
|
public
|
274
390
|
def exists?(&block)
|
@@ -289,24 +405,20 @@ module Bud
|
|
289
405
|
|
290
406
|
private
|
291
407
|
def prep_tuple(o)
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
elsif o.length > cols.length then
|
305
|
-
# if this tuple has more fields than usual, bundle up the
|
306
|
-
# extras into an array
|
307
|
-
o = (0..(cols.length - 1)).map{|c| o[c]} << (cols.length..(o.length - 1)).map{|c| o[c]}
|
408
|
+
return o if o.class == @struct
|
409
|
+
if o.class == Array
|
410
|
+
if @struct.nil?
|
411
|
+
sch = (1 .. o.length).map{|i| ("c"+i.to_s).to_sym}
|
412
|
+
init_schema(sch)
|
413
|
+
end
|
414
|
+
o = o.take(@structlen) if o.length > @structlen
|
415
|
+
elsif o.kind_of? Struct
|
416
|
+
init_schema(o.members.map{|m| m.to_sym}) if @struct.nil?
|
417
|
+
o = o.take(@structlen)
|
418
|
+
else
|
419
|
+
raise TypeError, "Array or struct type expected in \"#{qualified_tabname}\": #{o.inspect}"
|
308
420
|
end
|
309
|
-
return o
|
421
|
+
return @struct.new(*o)
|
310
422
|
end
|
311
423
|
|
312
424
|
private
|
@@ -316,8 +428,17 @@ module Bud
|
|
316
428
|
end
|
317
429
|
end
|
318
430
|
|
319
|
-
|
431
|
+
public
|
320
432
|
def do_insert(o, store)
|
433
|
+
if $BUD_DEBUG
|
434
|
+
storetype = case store.object_id
|
435
|
+
when @storage.object_id; "storage"
|
436
|
+
when @pending.object_id; "pending"
|
437
|
+
when @delta.object_id; "delta"
|
438
|
+
when @new_delta.object_id; "new_delta"
|
439
|
+
end
|
440
|
+
puts "#{qualified_tabname}.#{storetype} ==> #{o}"
|
441
|
+
end
|
321
442
|
return if o.nil? # silently ignore nils resulting from map predicates failing
|
322
443
|
o = prep_tuple(o)
|
323
444
|
key = get_key_vals(o)
|
@@ -331,8 +452,8 @@ module Bud
|
|
331
452
|
end
|
332
453
|
|
333
454
|
public
|
334
|
-
def insert(o) # :nodoc: all
|
335
|
-
# puts "insert: #{o
|
455
|
+
def insert(o, source=nil) # :nodoc: all
|
456
|
+
# puts "insert: #{o} into #{qualified_tabname}"
|
336
457
|
do_insert(o, @storage)
|
337
458
|
end
|
338
459
|
|
@@ -343,8 +464,8 @@ module Bud
|
|
343
464
|
|
344
465
|
private
|
345
466
|
def check_enumerable(o)
|
346
|
-
unless o.nil? or o.class < Enumerable
|
347
|
-
raise
|
467
|
+
unless o.nil? or o.class < Enumerable or o.class <= Proc
|
468
|
+
raise TypeError, "Collection #{qualified_tabname} expected Enumerable value, not #{o.inspect} (class = #{o.class})"
|
348
469
|
end
|
349
470
|
end
|
350
471
|
|
@@ -397,22 +518,47 @@ module Bud
|
|
397
518
|
end
|
398
519
|
|
399
520
|
public
|
400
|
-
def merge(o, buf=@
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
521
|
+
def merge(o, buf=@delta) # :nodoc: all
|
522
|
+
toplevel = @bud_instance.toplevel
|
523
|
+
if o.class <= Bud::PushElement
|
524
|
+
toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
|
525
|
+
deduce_schema(o) if @cols.nil?
|
526
|
+
o.wire_to self
|
527
|
+
elsif o.class <= Bud::BudCollection
|
528
|
+
toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
|
529
|
+
deduce_schema(o) if @cols.nil?
|
530
|
+
o.pro.wire_to self
|
531
|
+
elsif o.class <= Proc and toplevel.done_bootstrap and not toplevel.done_wiring and not o.nil?
|
532
|
+
toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
|
533
|
+
tbl = register_coll_expr(o)
|
534
|
+
tbl.pro.wire_to self
|
535
|
+
else
|
536
|
+
unless o.nil?
|
537
|
+
o = o.uniq.compact if o.respond_to?(:uniq)
|
538
|
+
check_enumerable(o)
|
539
|
+
establish_schema(o) if @cols.nil?
|
540
|
+
o.each {|i| do_insert(i, buf)}
|
411
541
|
end
|
412
542
|
end
|
413
543
|
return self
|
414
544
|
end
|
415
545
|
|
546
|
+
# def prep_coll_expr(o)
|
547
|
+
# o = o.uniq.compact if o.respond_to?(:uniq)
|
548
|
+
# check_enumerable(o)
|
549
|
+
# establish_schema(o) if @cols.nil?
|
550
|
+
# o
|
551
|
+
# end
|
552
|
+
|
553
|
+
def register_coll_expr(expr)
|
554
|
+
# require 'ruby-debug'; debugger
|
555
|
+
coll_name = ("expr_"+expr.object_id.to_s)
|
556
|
+
cols = (1..@cols.length).map{|i| ("c"+i.to_s).to_sym} unless @cols.nil?
|
557
|
+
@bud_instance.coll_expr(coll_name.to_sym, expr, cols)
|
558
|
+
coll = @bud_instance.send(coll_name)
|
559
|
+
coll
|
560
|
+
end
|
561
|
+
|
416
562
|
public
|
417
563
|
# instantaneously merge items from collection +o+ into +buf+
|
418
564
|
def <=(collection)
|
@@ -422,60 +568,121 @@ module Bud
|
|
422
568
|
# buffer items to be merged atomically at end of this timestep
|
423
569
|
public
|
424
570
|
def pending_merge(o) # :nodoc: all
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
571
|
+
toplevel = @bud_instance.toplevel
|
572
|
+
if o.class <= Bud::PushElement
|
573
|
+
toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
|
574
|
+
o.wire_to_pending self
|
575
|
+
elsif o.class <= Bud::BudCollection
|
576
|
+
toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
|
577
|
+
o.pro.wire_to_pending self
|
578
|
+
elsif o.class <= Proc and toplevel.done_bootstrap and not toplevel.done_wiring
|
579
|
+
toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
|
580
|
+
tbl = register_coll_expr(o) unless o.nil?
|
581
|
+
tbl.pro.wire_to_pending self
|
582
|
+
else
|
583
|
+
unless o.nil?
|
584
|
+
o = o.uniq.compact if o.respond_to?(:uniq)
|
585
|
+
check_enumerable(o)
|
586
|
+
establish_schema(o) if @cols.nil?
|
587
|
+
o.each{|i| self.do_insert(i, @pending)}
|
588
|
+
end
|
589
|
+
end
|
429
590
|
return self
|
430
591
|
end
|
431
592
|
|
593
|
+
public
|
594
|
+
def flush ; end
|
595
|
+
|
432
596
|
public
|
433
597
|
superator "<+" do |o|
|
434
598
|
pending_merge o
|
435
599
|
end
|
436
600
|
|
601
|
+
def tick
|
602
|
+
raise "tick must be overriden in #{self.class}"
|
603
|
+
end
|
604
|
+
|
605
|
+
# move deltas to storage, and new_deltas to deltas.
|
606
|
+
# return true if new deltas were found
|
437
607
|
public
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
608
|
+
def tick_deltas # :nodoc: all
|
609
|
+
unless @delta.empty?
|
610
|
+
puts "#{qualified_tabname}.tick_delta delta --> storage (#{@delta.size} elems)" if $BUD_DEBUG
|
611
|
+
@storage.merge!(@delta)
|
612
|
+
@tick_delta += @delta.values
|
613
|
+
@delta.clear
|
614
|
+
end
|
615
|
+
|
616
|
+
unless @new_delta.empty?
|
617
|
+
puts "#{qualified_tabname}.tick_delta new_delta --> delta (#{@new_delta.size} elems)" if $BUD_DEBUG
|
618
|
+
@new_delta.each_pair do |k, v|
|
619
|
+
sv = @storage[k]
|
620
|
+
if sv.nil?
|
621
|
+
@delta[k] = v
|
622
|
+
else
|
623
|
+
raise_pk_error(v, sv) unless v == sv
|
624
|
+
end
|
443
625
|
end
|
626
|
+
@new_delta.clear
|
627
|
+
return !(@delta.empty?)
|
444
628
|
end
|
629
|
+
return false # delta empty; another fixpoint iter not required.
|
445
630
|
end
|
446
631
|
|
447
632
|
public
|
448
|
-
|
449
|
-
|
633
|
+
def add_rescan_invalidate(rescan, invalidate)
|
634
|
+
# No change. Most collections don't need to rescan on every tick (only do so on negate). Also, there's no cache
|
635
|
+
# to invalidate by default. Scratches and PushElements override this method.
|
450
636
|
end
|
451
637
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
@pending = {}
|
458
|
-
raise Bud::Error, "orphaned tuples in @delta for #{@tabname}" unless @delta.empty?
|
459
|
-
raise Bud::Error, "orphaned tuples in @new_delta for #{@tabname}" unless @new_delta.empty?
|
638
|
+
def bootstrap
|
639
|
+
unless @pending.empty?
|
640
|
+
@delta = @pending
|
641
|
+
@pending = {}
|
642
|
+
end
|
460
643
|
end
|
461
644
|
|
462
|
-
# move deltas to storage, and new_deltas to deltas.
|
463
645
|
public
|
464
|
-
def
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
646
|
+
def flush_deltas
|
647
|
+
if $BUD_DEBUG
|
648
|
+
puts "#{qualified_tabname}.flush delta --> storage" unless @delta.empty?
|
649
|
+
puts "#{qualified_tabname}.flush new_delta --> storage" unless @new_delta.empty?
|
650
|
+
end
|
651
|
+
unless (@delta.empty?)
|
652
|
+
@storage.merge!(@delta)
|
653
|
+
@tick_delta += @delta.values
|
654
|
+
@delta.clear
|
655
|
+
end
|
656
|
+
unless @new_delta.empty?
|
657
|
+
@storage.merge!(@new_delta)
|
658
|
+
@new_delta.clear
|
659
|
+
end
|
660
|
+
# @tick_delta kept around for higher strata.
|
469
661
|
end
|
470
662
|
|
471
663
|
public
|
472
|
-
def
|
473
|
-
|
664
|
+
def to_push_elem(the_name=tabname, the_schema=schema)
|
665
|
+
# if no push source yet, set one up
|
666
|
+
toplevel = @bud_instance.toplevel
|
667
|
+
#rule_context = toplevel.this_rule_context
|
668
|
+
this_stratum = toplevel.this_stratum
|
669
|
+
oid = self.object_id
|
670
|
+
unless toplevel.scanners[this_stratum][[oid, the_name]]
|
671
|
+
toplevel.scanners[this_stratum][[oid, the_name]] = Bud::ScannerElement.new(the_name, self.bud_instance, self, the_schema)
|
672
|
+
toplevel.push_sources[this_stratum][[oid, the_name]] = toplevel.scanners[this_stratum][[oid, the_name]]
|
673
|
+
end
|
674
|
+
return toplevel.scanners[this_stratum][[oid, the_name]]
|
474
675
|
end
|
475
676
|
|
476
|
-
|
477
|
-
def
|
478
|
-
|
677
|
+
private
|
678
|
+
def method_missing(sym, *args, &block)
|
679
|
+
begin
|
680
|
+
@storage.send sym, *args, &block
|
681
|
+
rescue Exception => e
|
682
|
+
err = NoMethodError.new("no method :#{sym} in class #{self.class.name}")
|
683
|
+
err.set_backtrace(e.backtrace)
|
684
|
+
raise err
|
685
|
+
end
|
479
686
|
end
|
480
687
|
|
481
688
|
######## aggs
|
@@ -485,73 +692,26 @@ module Bud
|
|
485
692
|
# never deal with deltas. This assumes that stratification is done right, and it will
|
486
693
|
# be sensitive to bugs in the stratification!
|
487
694
|
def agg_in
|
488
|
-
if not respond_to?(:bud_instance) or bud_instance.nil?
|
695
|
+
if not respond_to?(:bud_instance) or bud_instance.nil?
|
489
696
|
return self
|
490
697
|
else
|
491
698
|
return []
|
492
699
|
end
|
493
700
|
end
|
494
701
|
|
702
|
+
|
495
703
|
# a generalization of argmin/argmax to arbitrary exemplary aggregates.
|
496
704
|
# for each distinct value of the grouping key columns, return the items in that group
|
497
705
|
# that have the value of the exemplary aggregate +aggname+
|
498
706
|
public
|
499
707
|
def argagg(aggname, gbkey_cols, collection)
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
end
|
508
|
-
end
|
509
|
-
if collection.class == Symbol
|
510
|
-
colnum = self.send(collection.to_s)[1]
|
511
|
-
else
|
512
|
-
colnum = collection[1]
|
513
|
-
end
|
514
|
-
tups = agg_in.inject({}) do |memo,p|
|
515
|
-
pkey_cols = keynames.map{|n| p.send(n.to_sym)}
|
516
|
-
if memo[pkey_cols].nil?
|
517
|
-
memo[pkey_cols] = {:agg=>agg.send(:init, p[colnum]), :tups => [p]}
|
518
|
-
else
|
519
|
-
memo[pkey_cols][:agg], argflag = \
|
520
|
-
agg.send(:trans, memo[pkey_cols][:agg], p[colnum])
|
521
|
-
if argflag == :keep or agg.send(:tie, memo[pkey_cols][:agg], p[colnum])
|
522
|
-
memo[pkey_cols][:tups] << p
|
523
|
-
elsif argflag == :replace
|
524
|
-
memo[pkey_cols][:tups] = [p]
|
525
|
-
elsif argflag.class <= Array and argflag[0] == :delete
|
526
|
-
memo[pkey_cols][:tups] -= argflag[1..-1]
|
527
|
-
end
|
528
|
-
end
|
529
|
-
memo
|
530
|
-
end
|
531
|
-
|
532
|
-
# now we need to finalize the agg per group
|
533
|
-
finalaggs = {}
|
534
|
-
finals = []
|
535
|
-
tups.each do |k,v|
|
536
|
-
finalaggs[k] = agg.send(:final, v[:agg])
|
537
|
-
end
|
538
|
-
|
539
|
-
# and winnow the tups to match
|
540
|
-
finalaggs.each do |k,v|
|
541
|
-
tups[k][:tups].each do |t|
|
542
|
-
finals << t if (t[colnum] == v)
|
543
|
-
end
|
544
|
-
end
|
545
|
-
|
546
|
-
if block_given?
|
547
|
-
finals.map{|r| yield r}
|
548
|
-
else
|
549
|
-
# merge directly into retval.storage, so that the temp tuples get picked up
|
550
|
-
# by the lhs of the rule
|
551
|
-
retval = BudScratch.new('argagg_temp', bud_instance, @given_schema)
|
552
|
-
retval.uniquify_tabname
|
553
|
-
retval.merge(finals, retval.storage)
|
554
|
-
end
|
708
|
+
elem = to_push_elem
|
709
|
+
elem.schema
|
710
|
+
gbkey_cols = gbkey_cols.map{|k| canonicalize_col(k)} unless gbkey_cols.nil?
|
711
|
+
retval = elem.argagg(aggname,gbkey_cols,canonicalize_col(collection))
|
712
|
+
# PushElement inherits the schema accessors from this Collection
|
713
|
+
retval.extend @cols_access
|
714
|
+
retval
|
555
715
|
end
|
556
716
|
|
557
717
|
# for each distinct value of the grouping key columns, return the items in
|
@@ -579,92 +739,54 @@ module Bud
|
|
579
739
|
end
|
580
740
|
end
|
581
741
|
|
582
|
-
def join(collections, *preds, &blk)
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
end
|
742
|
+
# def join(collections, *preds, &blk)
|
743
|
+
# # since joins are stateful, we want to allocate them once and store in this Bud instance
|
744
|
+
# # we ID them on their tablenames, preds, and block
|
745
|
+
# return wrap_map(BudJoin.new(collections, @bud_instance, preds), &blk)
|
746
|
+
# end
|
587
747
|
|
588
748
|
# form a collection containing all pairs of items in +self+ and items in
|
589
749
|
# +collection+
|
590
750
|
public
|
591
751
|
def *(collection)
|
592
|
-
|
752
|
+
elem1 = to_push_elem
|
753
|
+
j = elem1.join(collection)
|
754
|
+
return j
|
755
|
+
# join([self, collection])
|
593
756
|
end
|
594
757
|
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
758
|
+
def group(key_cols, *aggpairs, &blk)
|
759
|
+
elem = to_push_elem
|
760
|
+
key_cols = key_cols.map{|k| canonicalize_col(k)} unless key_cols.nil?
|
761
|
+
aggpairs = aggpairs.map{|ap| [ap[0], canonicalize_col(ap[1])].compact} unless aggpairs.nil?
|
762
|
+
g = elem.group(key_cols, *aggpairs, &blk)
|
763
|
+
return g
|
599
764
|
end
|
600
765
|
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
key_cols ||= []
|
607
|
-
keynames = key_cols.map do |k|
|
608
|
-
if k.class == Symbol
|
609
|
-
k
|
610
|
-
elsif k[2] and k[2].class == Symbol
|
611
|
-
k[2]
|
612
|
-
else
|
613
|
-
raise Bud::CompileError, "invalid grouping key"
|
614
|
-
end
|
615
|
-
end
|
616
|
-
aggcolsdups = aggpairs.map{|ap| ap[0].class.name.split("::").last}
|
617
|
-
aggcols = []
|
618
|
-
aggcolsdups.each_with_index do |n, i|
|
619
|
-
aggcols << "#{n.downcase}_#{i}".to_sym
|
620
|
-
end
|
621
|
-
aggpairs = aggpairs.map do |ap|
|
622
|
-
if ap[1].class == Symbol
|
623
|
-
colnum = ap[1].nil? ? nil : self.send(ap[1].to_s)[1]
|
624
|
-
else
|
625
|
-
colnum = ap[1].nil? ? nil : ap[1][1]
|
626
|
-
end
|
627
|
-
[ap[0], colnum]
|
628
|
-
end
|
629
|
-
tups = agg_in.inject({}) do |memo, p|
|
630
|
-
pkey_cols = keynames.map{|n| p.send(n)}
|
631
|
-
memo[pkey_cols] = [] if memo[pkey_cols].nil?
|
632
|
-
aggpairs.each_with_index do |ap, i|
|
633
|
-
agg = ap[0]
|
634
|
-
colval = ap[1].nil? ? nil : p[ap[1]]
|
635
|
-
if memo[pkey_cols][i].nil?
|
636
|
-
memo[pkey_cols][i] = agg.send(:init, colval)
|
637
|
-
else
|
638
|
-
memo[pkey_cols][i], ignore = agg.send(:trans, memo[pkey_cols][i], colval)
|
639
|
-
end
|
640
|
-
end
|
641
|
-
memo
|
642
|
-
end
|
766
|
+
def notin(collection, *preds, &blk)
|
767
|
+
elem1 = to_push_elem
|
768
|
+
elem2 = collection.to_push_elem
|
769
|
+
return elem1.notin(elem2, preds, &blk)
|
770
|
+
end
|
643
771
|
|
644
|
-
|
645
|
-
|
646
|
-
aggpairs.each_with_index do |ap, i|
|
647
|
-
finals << ap[0].send(:final, t[1][i])
|
648
|
-
end
|
649
|
-
memo << t[0] + finals
|
650
|
-
end
|
651
|
-
if block_given?
|
652
|
-
result.map{|r| yield r}
|
653
|
-
else
|
654
|
-
# merge directly into retval.storage, so that the temp tuples get picked up
|
655
|
-
# by the lhs of the rule
|
656
|
-
if aggcols.empty?
|
657
|
-
schema = keynames
|
658
|
-
else
|
659
|
-
schema = { keynames => aggcols }
|
660
|
-
end
|
661
|
-
retval = BudScratch.new('temp_group', bud_instance, schema)
|
662
|
-
retval.uniquify_tabname
|
663
|
-
retval.merge(result, retval.storage)
|
664
|
-
end
|
772
|
+
def canonicalize_col(col)
|
773
|
+
col.class <= Symbol ? self.send(col) : col
|
665
774
|
end
|
666
775
|
|
667
|
-
alias reduce inject
|
776
|
+
# alias reduce inject
|
777
|
+
def reduce(initial, &blk)
|
778
|
+
elem1 = to_push_elem
|
779
|
+
red_elem = elem1.reduce(initial, &blk)
|
780
|
+
return red_elem
|
781
|
+
end
|
782
|
+
|
783
|
+
public
|
784
|
+
def pretty_print_instance_variables
|
785
|
+
# list of attributes (in order) to print when pretty_print is called.
|
786
|
+
important = ["@tabname", "@storage", "@delta", "@new_delta", "@pending"]
|
787
|
+
# everything except bud_instance
|
788
|
+
important + (self.instance_variables - important - ["@bud_instance"])
|
789
|
+
end
|
668
790
|
|
669
791
|
public
|
670
792
|
def uniquify_tabname # :nodoc: all
|
@@ -674,11 +796,56 @@ module Bud
|
|
674
796
|
end
|
675
797
|
|
676
798
|
class BudScratch < BudCollection # :nodoc: all
|
799
|
+
public
|
800
|
+
def tick # :nodoc: all
|
801
|
+
@tick_delta.clear
|
802
|
+
@delta.clear
|
803
|
+
if not @pending.empty?
|
804
|
+
invalidate_cache
|
805
|
+
@delta = @pending
|
806
|
+
@pending = {}
|
807
|
+
elsif is_source
|
808
|
+
invalidate_cache
|
809
|
+
end
|
810
|
+
raise Bud::Error, "orphaned tuples in @new_delta for #{qualified_tabname}" unless @new_delta.empty?
|
811
|
+
end
|
812
|
+
|
813
|
+
public
|
814
|
+
def invalidate_at_tick
|
815
|
+
is_source # rescan always only if this scratch is a source.
|
816
|
+
end
|
817
|
+
|
818
|
+
|
819
|
+
public
|
820
|
+
def add_rescan_invalidate(rescan, invalidate)
|
821
|
+
srcs = non_temporal_predecessors
|
822
|
+
if srcs.any? {|e| rescan.member? e}
|
823
|
+
invalidate << self
|
824
|
+
srcs.each{|e| rescan << e}
|
825
|
+
end
|
826
|
+
end
|
827
|
+
|
828
|
+
public
|
829
|
+
def invalidate_cache
|
830
|
+
puts "#{qualified_tabname} invalidated" if $BUD_DEBUG
|
831
|
+
#for scratches, storage is a cached value.
|
832
|
+
@invalidated = true
|
833
|
+
@storage.clear
|
834
|
+
end
|
835
|
+
end
|
836
|
+
|
837
|
+
class BudInputInterface < BudScratch
|
677
838
|
end
|
678
839
|
|
679
|
-
class
|
840
|
+
class BudOutputInterface < BudScratch
|
680
841
|
end
|
681
842
|
|
843
|
+
class BudTemp < BudScratch # :nodoc: all
|
844
|
+
end
|
845
|
+
|
846
|
+
# Channels are a different type of collection in that they represent two distinct collections, one each for
|
847
|
+
# incoming and outgoing. The incoming side makes use of @storage and @delta, whereas the outgoing side only deals
|
848
|
+
# with @pending. XXX Maybe we should be using aliases instead.
|
682
849
|
class BudChannel < BudCollection
|
683
850
|
attr_reader :locspec_idx # :nodoc: all
|
684
851
|
|
@@ -694,7 +861,7 @@ module Bud
|
|
694
861
|
given_schema = Marshal.load(Marshal.dump(given_schema))
|
695
862
|
|
696
863
|
unless @is_loopback
|
697
|
-
the_cols, the_key_cols = parse_schema(given_schema)
|
864
|
+
the_cols, the_key_cols = BudCollection.parse_schema(given_schema)
|
698
865
|
spec_count = the_cols.count {|c| c.to_s.start_with? "@"}
|
699
866
|
if spec_count == 0
|
700
867
|
raise Bud::Error, "missing location specifier for channel '#{name}'"
|
@@ -720,6 +887,11 @@ module Bud
|
|
720
887
|
super(name, bud_instance, given_schema)
|
721
888
|
end
|
722
889
|
|
890
|
+
def bootstrap
|
891
|
+
# override BudCollection; pending should not be moved into delta.
|
892
|
+
end
|
893
|
+
|
894
|
+
|
723
895
|
private
|
724
896
|
def remove_at_sign!(cols)
|
725
897
|
i = cols.find_index {|c| c.to_s.start_with? "@"}
|
@@ -736,7 +908,7 @@ module Bud
|
|
736
908
|
lsplit[1] = lsplit[1].to_i
|
737
909
|
return lsplit
|
738
910
|
rescue Exception => e
|
739
|
-
raise Bud::Error, "
|
911
|
+
raise Bud::Error, "Illegal location specifier in tuple #{t.inspect} for channel \"#{qualified_tabname}\": #{e.to_s}"
|
740
912
|
end
|
741
913
|
end
|
742
914
|
|
@@ -747,24 +919,31 @@ module Bud
|
|
747
919
|
|
748
920
|
public
|
749
921
|
def tick # :nodoc: all
|
750
|
-
@storage
|
922
|
+
@storage.clear
|
923
|
+
@invalidated = true
|
751
924
|
# Note that we do not clear @pending here: if the user inserted into the
|
752
925
|
# channel manually (e.g., via <~ from inside a sync_do block), we send the
|
753
926
|
# message at the end of the current tick.
|
754
927
|
end
|
755
928
|
|
929
|
+
public
|
930
|
+
def invalidate_cache
|
931
|
+
end
|
932
|
+
|
756
933
|
public
|
757
934
|
def flush # :nodoc: all
|
758
|
-
|
759
|
-
|
760
|
-
|
935
|
+
toplevel = @bud_instance.toplevel
|
936
|
+
ip = toplevel.ip
|
937
|
+
port = toplevel.port
|
938
|
+
@pending.each_value do |t|
|
761
939
|
if @is_loopback
|
762
940
|
the_locspec = [ip, port]
|
763
941
|
else
|
764
942
|
the_locspec = split_locspec(t, @locspec_idx)
|
765
943
|
raise Bud::Error, "'#{t[@locspec_idx]}', channel '#{@tabname}'" if the_locspec[0].nil? or the_locspec[1].nil? or the_locspec[0] == '' or the_locspec[1] == ''
|
766
944
|
end
|
767
|
-
|
945
|
+
puts "channel #{qualified_tabname}.send: #{t}" if $BUD_DEBUG
|
946
|
+
toplevel.dsock.send_datagram([qualified_tabname.to_s, t].to_msgpack, the_locspec[0], the_locspec[1])
|
768
947
|
end
|
769
948
|
@pending.clear
|
770
949
|
end
|
@@ -777,9 +956,9 @@ module Bud
|
|
777
956
|
if cols.size > 2
|
778
957
|
# bundle up each tuple's non-locspec fields into an array
|
779
958
|
retval = case @locspec_idx
|
780
|
-
when 0 then self.pro{|t| t
|
781
|
-
when (
|
782
|
-
else self.pro{|t| t
|
959
|
+
when 0 then self.pro{|t| t.values_at(1..(t.size-1))}
|
960
|
+
when (schema.size - 1) then self.pro{|t| t.values_at(0..(t.size-2))}
|
961
|
+
else self.pro{|t| t.values_at(0..(@locspec_idx-1), @locspec_idx+1..(t.size-1))}
|
783
962
|
end
|
784
963
|
else
|
785
964
|
# just return each tuple's non-locspec field value
|
@@ -789,7 +968,11 @@ module Bud
|
|
789
968
|
end
|
790
969
|
|
791
970
|
superator "<~" do |o|
|
792
|
-
|
971
|
+
if o.class <= PushElement
|
972
|
+
o.wire_to_pending self
|
973
|
+
else
|
974
|
+
pending_merge(o)
|
975
|
+
end
|
793
976
|
end
|
794
977
|
|
795
978
|
superator "<+" do |o|
|
@@ -803,7 +986,7 @@ module Bud
|
|
803
986
|
end
|
804
987
|
end
|
805
988
|
|
806
|
-
class BudTerminal <
|
989
|
+
class BudTerminal < BudScratch # :nodoc: all
|
807
990
|
def initialize(name, given_schema, bud_instance, prompt=false) # :nodoc: all
|
808
991
|
super(name, bud_instance, given_schema)
|
809
992
|
@prompt = prompt
|
@@ -815,18 +998,19 @@ module Bud
|
|
815
998
|
# we should add the terminal file descriptor to the EM event loop.
|
816
999
|
@reader = Thread.new do
|
817
1000
|
begin
|
1001
|
+
toplevel = @bud_instance.toplevel
|
818
1002
|
while true
|
819
1003
|
out_io = get_out_io
|
820
1004
|
out_io.print("#{tabname} > ") if @prompt
|
821
1005
|
|
822
|
-
in_io =
|
1006
|
+
in_io = toplevel.options[:stdin]
|
823
1007
|
s = in_io.gets
|
824
1008
|
break if s.nil? # Hit EOF
|
825
1009
|
s = s.chomp if s
|
826
1010
|
tup = [s]
|
827
1011
|
|
828
|
-
ip =
|
829
|
-
port =
|
1012
|
+
ip = toplevel.ip
|
1013
|
+
port = toplevel.port
|
830
1014
|
EventMachine::schedule do
|
831
1015
|
socket = EventMachine::open_datagram_socket("127.0.0.1", 0)
|
832
1016
|
socket.send_datagram([tabname, tup].to_msgpack, ip, port)
|
@@ -843,19 +1027,37 @@ module Bud
|
|
843
1027
|
public
|
844
1028
|
def flush #:nodoc: all
|
845
1029
|
out_io = get_out_io
|
846
|
-
@pending.
|
1030
|
+
@pending.each_value do |p|
|
847
1031
|
out_io.puts p[0]
|
848
1032
|
out_io.flush
|
849
1033
|
end
|
850
|
-
@pending
|
1034
|
+
@pending.clear
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
public
|
1038
|
+
def invalidate_at_tick
|
1039
|
+
true
|
851
1040
|
end
|
852
1041
|
|
853
1042
|
public
|
854
1043
|
def tick #:nodoc: all
|
855
|
-
@
|
1044
|
+
unless @pending.empty?
|
1045
|
+
@delta = @pending # pending used for input tuples in this case.
|
1046
|
+
@tick_delta = @pending.values
|
1047
|
+
@pending.clear
|
1048
|
+
else
|
1049
|
+
@storage.clear
|
1050
|
+
@delta.clear
|
1051
|
+
@tick_delta.clear
|
1052
|
+
end
|
1053
|
+
@invalidated = true # channels and terminals are always invalidated.
|
856
1054
|
raise Bud::Error, "orphaned pending tuples in terminal" unless @pending.empty?
|
857
1055
|
end
|
858
1056
|
|
1057
|
+
public
|
1058
|
+
def invalidate_cache
|
1059
|
+
end
|
1060
|
+
|
859
1061
|
undef merge
|
860
1062
|
|
861
1063
|
public
|
@@ -864,19 +1066,23 @@ module Bud
|
|
864
1066
|
end
|
865
1067
|
|
866
1068
|
superator "<~" do |o|
|
867
|
-
|
1069
|
+
if o.class <= PushElement
|
1070
|
+
o.wire_to_pending self
|
1071
|
+
else
|
1072
|
+
pending_merge(o)
|
1073
|
+
end
|
868
1074
|
end
|
869
1075
|
|
870
1076
|
private
|
871
1077
|
def get_out_io
|
872
|
-
rv = @bud_instance.options[:stdout]
|
1078
|
+
rv = @bud_instance.toplevel.options[:stdout]
|
873
1079
|
rv ||= $stdout
|
874
1080
|
raise Bud::Error, "attempting to write to terminal #{tabname} that was already closed" if rv.closed?
|
875
1081
|
rv
|
876
1082
|
end
|
877
1083
|
end
|
878
1084
|
|
879
|
-
class BudPeriodic <
|
1085
|
+
class BudPeriodic < BudScratch # :nodoc: all
|
880
1086
|
def <=(o)
|
881
1087
|
raise Bud::Error, "illegal use of <= with periodic '#{tabname}' on left"
|
882
1088
|
end
|
@@ -892,43 +1098,142 @@ module Bud
|
|
892
1098
|
superator "<+" do |o|
|
893
1099
|
raise Bud::Error, "illegal use of <+ with periodic '#{tabname}' on left"
|
894
1100
|
end
|
1101
|
+
|
1102
|
+
def tick
|
1103
|
+
@tick_delta.clear
|
1104
|
+
@delta.clear
|
1105
|
+
@invalidated = true
|
1106
|
+
unless pending.empty?
|
1107
|
+
@delta = @pending
|
1108
|
+
@pending = {}
|
1109
|
+
end
|
1110
|
+
end
|
1111
|
+
end
|
1112
|
+
|
1113
|
+
class BudPersistentCollection < BudCollection
|
1114
|
+
public
|
1115
|
+
def invalidate_at_tick
|
1116
|
+
false # rescan required only when negated.
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
public
|
1120
|
+
def invalidate_cache
|
1121
|
+
raise "Abstract method not implemented by derived class #{self.class}"
|
1122
|
+
end
|
895
1123
|
end
|
896
1124
|
|
897
|
-
class BudTable <
|
1125
|
+
class BudTable < BudPersistentCollection # :nodoc: all
|
898
1126
|
def initialize(name, bud_instance, given_schema) # :nodoc: all
|
899
1127
|
super(name, bud_instance, given_schema)
|
900
1128
|
@to_delete = []
|
1129
|
+
@to_delete_by_key = []
|
901
1130
|
end
|
902
1131
|
|
903
1132
|
public
|
904
1133
|
def tick #:nodoc: all
|
1134
|
+
if $BUD_DEBUG
|
1135
|
+
puts "#{tabname}. storage -= pending deletes" unless @to_delete.empty? and @to_delete_by_key.empty?
|
1136
|
+
puts "#{tabname}. delta += pending" unless @pending.empty?
|
1137
|
+
end
|
1138
|
+
@tick_delta.clear
|
1139
|
+
deleted = nil
|
905
1140
|
@to_delete.each do |tuple|
|
906
|
-
|
907
|
-
if @storage[
|
908
|
-
@storage.delete
|
1141
|
+
keycols = @key_colnums.map{|k| tuple[k]}
|
1142
|
+
if @storage[keycols] == tuple
|
1143
|
+
v = @storage.delete keycols
|
1144
|
+
deleted ||= v
|
909
1145
|
end
|
910
1146
|
end
|
911
|
-
@
|
912
|
-
|
1147
|
+
@to_delete_by_key.each do |tuple|
|
1148
|
+
v = @storage.delete @key_colnums.map{|k| tuple[k]}
|
1149
|
+
deleted ||= v
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
@invalidated = (not deleted.nil?)
|
1153
|
+
puts "table #{qualified_tabname} invalidated" if $BUD_DEBUG and @invalidated
|
1154
|
+
|
1155
|
+
@pending.each do |keycols, tuple|
|
1156
|
+
old = @storage[keycols]
|
913
1157
|
if old.nil?
|
914
|
-
@
|
1158
|
+
@delta[keycols] = tuple #
|
915
1159
|
else
|
916
1160
|
raise_pk_error(tuple, old) unless tuple == old
|
917
1161
|
end
|
918
1162
|
end
|
919
1163
|
@to_delete = []
|
1164
|
+
@to_delete_by_key = []
|
920
1165
|
@pending = {}
|
921
1166
|
end
|
922
1167
|
|
1168
|
+
def invalidated=(val)
|
1169
|
+
raise "Internal error: nust not set invalidate on tables"
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
def pending_delete(o)
|
1173
|
+
toplevel = @bud_instance.toplevel
|
1174
|
+
if o.class <= Bud::PushElement
|
1175
|
+
toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
|
1176
|
+
o.wire_to_delete self
|
1177
|
+
elsif o.class <= Bud::BudCollection
|
1178
|
+
toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
|
1179
|
+
o.pro.wire_to_delete self
|
1180
|
+
elsif o.class <= Proc and @bud_instance.toplevel.done_bootstrap and not toplevel.done_wiring
|
1181
|
+
toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
|
1182
|
+
tbl = register_coll_expr(o)
|
1183
|
+
tbl.pro.wire_to_delete self
|
1184
|
+
else
|
1185
|
+
unless o.nil?
|
1186
|
+
o = o.uniq.compact if o.respond_to?(:uniq)
|
1187
|
+
check_enumerable(o)
|
1188
|
+
establish_schema(o) if @cols.nil?
|
1189
|
+
o.each{|i| @to_delete << prep_tuple(i)}
|
1190
|
+
end
|
1191
|
+
end
|
1192
|
+
end
|
923
1193
|
superator "<-" do |o|
|
924
|
-
o
|
925
|
-
|
926
|
-
|
1194
|
+
pending_delete(o)
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
public
|
1198
|
+
def pending_delete_keys(o)
|
1199
|
+
toplevel = @bud_instance.toplevel
|
1200
|
+
if o.class <= Bud::PushElement
|
1201
|
+
o.wire_to_delete_by_key self
|
1202
|
+
elsif o.class <= Bud::BudCollection
|
1203
|
+
o.pro.wire_to_delete_by_key self
|
1204
|
+
elsif o.class <= Proc and @bud_instance.toplevel.done_bootstrap and not @bud_instance.toplevel.done_wiring
|
1205
|
+
tbl = register_coll_expr(o)
|
1206
|
+
tbl.pro.wire_to_delete_by_key self
|
1207
|
+
else
|
1208
|
+
unless o.nil?
|
1209
|
+
o = o.uniq.compact if o.respond_to?(:uniq)
|
1210
|
+
check_enumerable(o)
|
1211
|
+
establish_schema(o) if @cols.nil?
|
1212
|
+
o.each{|i| @to_delete_by_key << prep_tuple(i)}
|
1213
|
+
end
|
927
1214
|
end
|
1215
|
+
o
|
1216
|
+
end
|
1217
|
+
|
1218
|
+
public
|
1219
|
+
def invalidate_cache
|
1220
|
+
# no cache to invalidate. Also, tables do not invalidate dependents, because their own state is not considered
|
1221
|
+
# invalidated; that happens only if there were pending deletes at the beginning of a tick (see tick())
|
1222
|
+
puts "******** invalidate_cache called on BudTable"
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
public
|
1226
|
+
superator "<+-" do |o|
|
1227
|
+
pending_delete_keys(o)
|
1228
|
+
self <+ o
|
1229
|
+
end
|
1230
|
+
public
|
1231
|
+
superator "<-+" do |o|
|
1232
|
+
self <+- o
|
928
1233
|
end
|
929
1234
|
end
|
930
1235
|
|
931
|
-
class BudReadOnly <
|
1236
|
+
class BudReadOnly < BudCollection # :nodoc: all
|
932
1237
|
superator "<+" do |o|
|
933
1238
|
raise CompileError, "illegal use of <+ with read-only collection '#{@tabname}' on left"
|
934
1239
|
end
|
@@ -936,6 +1241,50 @@ module Bud
|
|
936
1241
|
def merge(o) #:nodoc: all
|
937
1242
|
raise CompileError, "illegal use of <= with read-only collection '#{@tabname}' on left"
|
938
1243
|
end
|
1244
|
+
public
|
1245
|
+
def invalidate_cache
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
public
|
1249
|
+
def invalidate_at_tick
|
1250
|
+
true
|
1251
|
+
end
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
class BudSignal < BudReadOnly
|
1255
|
+
def invalidate_at_tick
|
1256
|
+
true
|
1257
|
+
end
|
1258
|
+
def tick
|
1259
|
+
@invalidated = true
|
1260
|
+
@storage.clear
|
1261
|
+
unless @pending.empty?
|
1262
|
+
@delta = @pending
|
1263
|
+
@pending = {}
|
1264
|
+
end
|
1265
|
+
end
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
class BudCollExpr < BudReadOnly # :nodoc: all
|
1269
|
+
def initialize(name, bud_instance, expr, given_schema=nil, defer_schema=false)
|
1270
|
+
super(name, bud_instance, given_schema, defer_schema)
|
1271
|
+
@expr = expr
|
1272
|
+
@invalidated = true
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
def tick
|
1276
|
+
@invalidated = true
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
public
|
1280
|
+
def each(&block)
|
1281
|
+
@expr.call.each {|i| yield i}
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
public
|
1285
|
+
def each_raw(&block)
|
1286
|
+
each(&block)
|
1287
|
+
end
|
939
1288
|
end
|
940
1289
|
|
941
1290
|
class BudFileReader < BudReadOnly # :nodoc: all
|
@@ -949,22 +1298,36 @@ module Bud
|
|
949
1298
|
end
|
950
1299
|
|
951
1300
|
public
|
952
|
-
def
|
953
|
-
if @bud_instance.stratum_first_iter
|
954
|
-
return map(&blk)
|
955
|
-
else
|
956
|
-
return []
|
957
|
-
end
|
958
|
-
end
|
959
|
-
|
960
|
-
public
|
961
|
-
def each(&block) # :nodoc: all
|
1301
|
+
def each_raw(&block) # :nodoc: all
|
962
1302
|
while (l = @fd.gets)
|
963
|
-
t =
|
1303
|
+
t = [@linenum, l.strip]
|
964
1304
|
@linenum += 1
|
965
1305
|
tick_metrics if bud_instance.options[:metrics]
|
966
1306
|
yield t
|
967
1307
|
end
|
968
1308
|
end
|
1309
|
+
|
1310
|
+
public
|
1311
|
+
def each(&blk)
|
1312
|
+
each_raw {|l| tuple_accessors(blk.call(l))}
|
1313
|
+
end
|
1314
|
+
end
|
1315
|
+
end
|
1316
|
+
|
1317
|
+
module Enumerable
|
1318
|
+
# public
|
1319
|
+
# # monkeypatch to Enumerable to rename collections and their schemas
|
1320
|
+
# def rename(new_tabname, new_schema=nil)
|
1321
|
+
# scr = Bud::BudScratch.new(new_tabname.to_s, nil, new_schema)
|
1322
|
+
# scr.merge(self, scr.storage)
|
1323
|
+
# scr
|
1324
|
+
# end
|
1325
|
+
|
1326
|
+
public
|
1327
|
+
# We rewrite "map" calls in Bloom blocks to invoke the "pro" method
|
1328
|
+
# instead. This is fine when applied to a BudCollection; when applied to a
|
1329
|
+
# normal Enumerable, just treat pro as an alias for map.
|
1330
|
+
def pro(&blk)
|
1331
|
+
map(&blk)
|
969
1332
|
end
|
970
1333
|
end
|