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.
data/lib/bmg/operator.rb CHANGED
@@ -46,4 +46,5 @@ require_relative 'operator/rename'
46
46
  require_relative 'operator/restrict'
47
47
  require_relative 'operator/rxmatch'
48
48
  require_relative 'operator/summarize'
49
+ require_relative 'operator/transform'
49
50
  require_relative 'operator/union'
@@ -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?
@@ -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
@@ -45,13 +45,7 @@ module Bmg
45
45
  protected ### inspect
46
46
 
47
47
  def comparator
48
- ->(t1, t2) {
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
@@ -9,3 +9,4 @@ module Bmg
9
9
  end
10
10
  require_relative "reader/csv"
11
11
  require_relative "reader/excel"
12
+ require_relative "reader/text_file"
@@ -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
- Writer::Csv.new(options).call(self, string_or_io)
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
@@ -24,7 +24,7 @@ module Bmg
24
24
  protected :type=
25
25
 
26
26
  def each(&bl)
27
- spy.call(self)
27
+ spy.call(self) if bl
28
28
  operand.each(&bl)
29
29
  end
30
30
 
@@ -10,7 +10,6 @@ module Bmg
10
10
  end
11
11
 
12
12
  attr_accessor :type
13
- protected :type=
14
13
 
15
14
  protected
16
15