bmg 0.17.5 → 0.18.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -3
- data/README.md +240 -57
- data/lib/bmg.rb +7 -0
- data/lib/bmg/algebra.rb +11 -0
- data/lib/bmg/algebra/shortcuts.rb +14 -0
- data/lib/bmg/operator.rb +1 -0
- data/lib/bmg/operator/allbut.rb +22 -0
- data/lib/bmg/operator/autosummarize.rb +20 -4
- data/lib/bmg/operator/autowrap.rb +8 -0
- data/lib/bmg/operator/image.rb +26 -2
- data/lib/bmg/operator/page.rb +1 -7
- data/lib/bmg/operator/transform.rb +94 -0
- data/lib/bmg/reader.rb +1 -0
- data/lib/bmg/reader/text_file.rb +56 -0
- data/lib/bmg/relation.rb +13 -2
- data/lib/bmg/relation/proxy.rb +63 -0
- data/lib/bmg/relation/spied.rb +1 -1
- data/lib/bmg/sql/relation.rb +0 -1
- data/lib/bmg/support.rb +3 -0
- data/lib/bmg/support/keys.rb +4 -0
- data/lib/bmg/support/ordering.rb +20 -0
- data/lib/bmg/support/output_preferences.rb +44 -0
- data/lib/bmg/support/tuple_algebra.rb +6 -0
- data/lib/bmg/support/tuple_transformer.rb +63 -0
- data/lib/bmg/type.rb +25 -0
- data/lib/bmg/version.rb +2 -2
- data/lib/bmg/writer/csv.rb +18 -7
- data/tasks/test.rake +9 -2
- metadata +21 -15
data/lib/bmg/operator.rb
CHANGED
data/lib/bmg/operator/allbut.rb
CHANGED
@@ -63,12 +63,34 @@ module Bmg
|
|
63
63
|
|
64
64
|
protected ### optimization
|
65
65
|
|
66
|
+
def _allbut(type, butlist)
|
67
|
+
operand.allbut(self.butlist|butlist)
|
68
|
+
end
|
69
|
+
|
70
|
+
def _matching(type, right, on)
|
71
|
+
# Always possible to push the matching, since by construction
|
72
|
+
# `on` can only use attributes that have not been trown away,
|
73
|
+
# hence they exist on `operand` too.
|
74
|
+
operand.matching(right, on).allbut(butlist)
|
75
|
+
end
|
76
|
+
|
66
77
|
def _restrict(type, predicate)
|
67
78
|
operand.restrict(predicate).allbut(butlist)
|
68
79
|
end
|
69
80
|
|
81
|
+
def _page(type, ordering, page_index, options)
|
82
|
+
return super unless self.preserving_key?
|
83
|
+
operand.page(ordering, page_index, options).allbut(butlist)
|
84
|
+
end
|
85
|
+
|
70
86
|
protected ### inspect
|
71
87
|
|
88
|
+
def preserving_key?
|
89
|
+
operand.type.knows_keys? && operand.type.keys.find{|k|
|
90
|
+
(k & butlist).empty?
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
72
94
|
def args
|
73
95
|
[ butlist ]
|
74
96
|
end
|
@@ -24,6 +24,22 @@ module Bmg
|
|
24
24
|
|
25
25
|
public
|
26
26
|
|
27
|
+
def self.same(*args)
|
28
|
+
Same.new(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.group(*args)
|
32
|
+
Group.new(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.y_by_x(*args)
|
36
|
+
YByX.new(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.ys_by_x(*args)
|
40
|
+
YsByX.new(*args)
|
41
|
+
end
|
42
|
+
|
27
43
|
def each(&bl)
|
28
44
|
h = {}
|
29
45
|
@operand.each do |tuple|
|
@@ -175,11 +191,11 @@ module Bmg
|
|
175
191
|
end
|
176
192
|
|
177
193
|
def init(v)
|
178
|
-
[v]
|
194
|
+
v.nil? ? [] : [v]
|
179
195
|
end
|
180
196
|
|
181
197
|
def sum(v1, v2)
|
182
|
-
v1 << v2
|
198
|
+
v2.nil? ? v1 : (v1 << v2)
|
183
199
|
end
|
184
200
|
|
185
201
|
def term(v)
|
@@ -211,11 +227,11 @@ module Bmg
|
|
211
227
|
end
|
212
228
|
|
213
229
|
def init(v)
|
214
|
-
[v]
|
230
|
+
v.nil? ? [] : [v]
|
215
231
|
end
|
216
232
|
|
217
233
|
def sum(v1, v2)
|
218
|
-
v1 << v2
|
234
|
+
v2.nil? ? v1 : (v1 << v2)
|
219
235
|
end
|
220
236
|
|
221
237
|
def term(v)
|
@@ -86,6 +86,14 @@ module Bmg
|
|
86
86
|
false
|
87
87
|
end
|
88
88
|
|
89
|
+
def _matching(type, right, on)
|
90
|
+
if (wrapped_roots! & on).empty?
|
91
|
+
operand.matching(right, on).autowrap(options)
|
92
|
+
else
|
93
|
+
super
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
89
97
|
def _page(type, ordering, page_index, opts)
|
90
98
|
attrs = ordering.map{|(a,d)| a }
|
91
99
|
if (wrapped_roots! & attrs).empty?
|
data/lib/bmg/operator/image.rb
CHANGED
@@ -99,9 +99,10 @@ module Bmg
|
|
99
99
|
key = tuple_project(t, on)
|
100
100
|
index[key].operand << tuple_image(t, on)
|
101
101
|
end
|
102
|
-
if options[:array]
|
102
|
+
if opt = options[:array]
|
103
|
+
sorter = to_sorter(opt)
|
103
104
|
index = index.each_with_object({}) do |(k,v),ix|
|
104
|
-
ix[k] = v.to_a
|
105
|
+
ix[k] = sorter ? v.to_a.sort(&sorter) : v.to_a
|
105
106
|
end
|
106
107
|
end
|
107
108
|
index
|
@@ -156,6 +157,24 @@ module Bmg
|
|
156
157
|
|
157
158
|
protected ### optimization
|
158
159
|
|
160
|
+
def _allbut(type, butlist)
|
161
|
+
if butlist.include?(as)
|
162
|
+
left.allbut(butlist - [as])
|
163
|
+
elsif (butlist & on).empty?
|
164
|
+
left.allbut(butlist).image(right, as, on, options)
|
165
|
+
else
|
166
|
+
super
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def _matching(type, m_right, m_on)
|
171
|
+
if m_on.include?(as)
|
172
|
+
super
|
173
|
+
else
|
174
|
+
left.matching(m_right, m_on).image(right, as, on, options)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
159
178
|
def _page(type, ordering, page_index, opts)
|
160
179
|
if ordering.map{|(k,v)| k}.include?(as)
|
161
180
|
super
|
@@ -227,6 +246,11 @@ module Bmg
|
|
227
246
|
Relation::InMemory.new(image_type, Set.new)
|
228
247
|
end
|
229
248
|
|
249
|
+
def to_sorter(opt)
|
250
|
+
return nil unless opt.is_a?(Array)
|
251
|
+
Ordering.new(opt).comparator
|
252
|
+
end
|
253
|
+
|
230
254
|
public
|
231
255
|
|
232
256
|
def to_s
|
data/lib/bmg/operator/page.rb
CHANGED
@@ -45,13 +45,7 @@ module Bmg
|
|
45
45
|
protected ### inspect
|
46
46
|
|
47
47
|
def comparator
|
48
|
-
|
49
|
-
ordering.each do |(attr,direction)|
|
50
|
-
c = t1[attr] <=> t2[attr]
|
51
|
-
return (direction == :desc ? -c : c) unless c==0
|
52
|
-
end
|
53
|
-
0
|
54
|
-
}
|
48
|
+
Ordering.new(@ordering).comparator
|
55
49
|
end
|
56
50
|
|
57
51
|
def args
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Bmg
|
2
|
+
module Operator
|
3
|
+
#
|
4
|
+
# Transform operator.
|
5
|
+
#
|
6
|
+
# Transforms existing attributes through computations
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# [{ a: 1 }] transform { a: ->(t){ t[:a]*2 } } => [{ a: 4 }]
|
11
|
+
#
|
12
|
+
class Transform
|
13
|
+
include Operator::Unary
|
14
|
+
|
15
|
+
DEFAULT_OPTIONS = {}
|
16
|
+
|
17
|
+
def initialize(type, operand, transformation, options = {})
|
18
|
+
@type = type
|
19
|
+
@operand = operand
|
20
|
+
@transformation = transformation
|
21
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
attr_reader :transformation, :options
|
27
|
+
|
28
|
+
public
|
29
|
+
|
30
|
+
def each
|
31
|
+
t = transformer
|
32
|
+
@operand.each do |tuple|
|
33
|
+
yield t.call(tuple)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_ast
|
38
|
+
[ :transform, operand.to_ast, transformation.dup ]
|
39
|
+
end
|
40
|
+
|
41
|
+
protected ### optimization
|
42
|
+
|
43
|
+
def _allbut(type, butlist)
|
44
|
+
# `allbut` can always be pushed down the tree. unlike
|
45
|
+
# `extend` the Proc that might be used cannot use attributes
|
46
|
+
# in butlist, so it's safe to strip them away.
|
47
|
+
if transformer.knows_attrlist?
|
48
|
+
# We just need to clean the transformation
|
49
|
+
attrlist = transformer.to_attrlist
|
50
|
+
thrown = attrlist & butlist
|
51
|
+
t = transformation.dup.reject{|k,v| thrown.include?(k) }
|
52
|
+
operand.allbut(butlist).transform(t, options)
|
53
|
+
else
|
54
|
+
operand.allbut(butlist).transform(transformation, options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def _project(type, attrlist)
|
59
|
+
if transformer.knows_attrlist?
|
60
|
+
t = transformation.dup.select{|k,v| attrlist.include?(k) }
|
61
|
+
operand.project(attrlist).transform(t, options)
|
62
|
+
else
|
63
|
+
operand.project(attrlist).transform(transformation, options)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def _restrict(type, predicate)
|
68
|
+
return super unless transformer.knows_attrlist?
|
69
|
+
top, bottom = predicate.and_split(transformer.to_attrlist)
|
70
|
+
if top == predicate
|
71
|
+
super
|
72
|
+
else
|
73
|
+
operand
|
74
|
+
.restrict(bottom)
|
75
|
+
.transform(transformation, options)
|
76
|
+
.restrict(top)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
protected ### inspect
|
81
|
+
|
82
|
+
def args
|
83
|
+
[ transformation ]
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def transformer
|
89
|
+
@transformer ||= TupleTransformer.new(transformation)
|
90
|
+
end
|
91
|
+
|
92
|
+
end # class Transform
|
93
|
+
end # module Operator
|
94
|
+
end # module Bmg
|
data/lib/bmg/reader.rb
CHANGED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Bmg
|
2
|
+
module Reader
|
3
|
+
class TextFile
|
4
|
+
include Reader
|
5
|
+
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
strip: true,
|
8
|
+
parse: nil
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize(type, path, options = {})
|
12
|
+
options = { parse: options } if options.is_a?(Regexp)
|
13
|
+
@path = path
|
14
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
15
|
+
@type = infer_type(type)
|
16
|
+
end
|
17
|
+
attr_reader :path, :options
|
18
|
+
|
19
|
+
public # Relation
|
20
|
+
|
21
|
+
def each
|
22
|
+
path.each_line.each_with_index do |text, line|
|
23
|
+
text = text.strip if strip?
|
24
|
+
parsed = parse(text)
|
25
|
+
yield({line: 1+line}.merge(parsed)) if parsed
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def infer_type(base)
|
32
|
+
return base unless base == Bmg::Type::ANY
|
33
|
+
attr_list = if rx = options[:parse]
|
34
|
+
[:line] + rx.names.map(&:to_sym)
|
35
|
+
else
|
36
|
+
[:line, :text]
|
37
|
+
end
|
38
|
+
base
|
39
|
+
.with_attrlist(attr_list)
|
40
|
+
.with_keys([[:line]])
|
41
|
+
end
|
42
|
+
|
43
|
+
def strip?
|
44
|
+
options[:strip]
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse(text)
|
48
|
+
return { text: text } unless rx = options[:parse]
|
49
|
+
if match = rx.match(text)
|
50
|
+
TupleAlgebra.symbolize_keys(match.named_captures)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end # class TextFile
|
55
|
+
end # module Reader
|
56
|
+
end # module Bmg
|
data/lib/bmg/relation.rb
CHANGED
@@ -17,6 +17,16 @@ module Bmg
|
|
17
17
|
self
|
18
18
|
end
|
19
19
|
|
20
|
+
def type
|
21
|
+
Bmg::Type::ANY
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_type(type)
|
25
|
+
dup.tap{|r|
|
26
|
+
r.type = type
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
20
30
|
def with_typecheck
|
21
31
|
dup.tap{|r|
|
22
32
|
r.type = r.type.with_typecheck
|
@@ -113,9 +123,10 @@ module Bmg
|
|
113
123
|
# When no string_or_io is used, the method uses a string.
|
114
124
|
#
|
115
125
|
# The method always returns the string_or_io.
|
116
|
-
def to_csv(options = {}, string_or_io = nil)
|
126
|
+
def to_csv(options = {}, string_or_io = nil, preferences = nil)
|
117
127
|
options, string_or_io = {}, options unless options.is_a?(Hash)
|
118
|
-
|
128
|
+
string_or_io, preferences = nil, string_or_io if string_or_io.is_a?(Hash)
|
129
|
+
Writer::Csv.new(options, preferences).call(self, string_or_io)
|
119
130
|
end
|
120
131
|
|
121
132
|
# Converts to an sexpr expression.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Bmg
|
2
|
+
module Relation
|
3
|
+
#
|
4
|
+
# This module can be used to create typed collection on top
|
5
|
+
# of Bmg relations. Algebra methods will be delegated to the
|
6
|
+
# decorated relation, and results wrapped in a new instance
|
7
|
+
# of the class.
|
8
|
+
#
|
9
|
+
module Proxy
|
10
|
+
|
11
|
+
def initialize(relation)
|
12
|
+
@relation = relation
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(name, *args, &bl)
|
16
|
+
if @relation.respond_to?(name)
|
17
|
+
res = @relation.send(name, *args, &bl)
|
18
|
+
res.is_a?(Relation) ? _proxy(res) : res
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_to?(name, *args)
|
25
|
+
@relation.respond_to?(name) || super
|
26
|
+
end
|
27
|
+
|
28
|
+
[
|
29
|
+
:extend
|
30
|
+
].each do |name|
|
31
|
+
define_method(name) do |*args, &bl|
|
32
|
+
res = @relation.send(name, *args, &bl)
|
33
|
+
res.is_a?(Relation) ? _proxy(res) : res
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
[
|
38
|
+
:one,
|
39
|
+
:one_or_nil
|
40
|
+
].each do |meth|
|
41
|
+
define_method(meth) do |*args, &bl|
|
42
|
+
res = @relation.send(meth, *args, &bl)
|
43
|
+
res.nil? ? nil : _proxy_tuple(res)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_json(*args, &bl)
|
48
|
+
@relation.to_json(*args, &bl)
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def _proxy(relation)
|
54
|
+
self.class.new(relation)
|
55
|
+
end
|
56
|
+
|
57
|
+
def _proxy_tuple(tuple)
|
58
|
+
tuple
|
59
|
+
end
|
60
|
+
|
61
|
+
end # module Proxy
|
62
|
+
end # class Relation
|
63
|
+
end # module Bmg
|
data/lib/bmg/relation/spied.rb
CHANGED