bmg 0.17.5 → 0.18.1
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.
- 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