bmg 0.17.5 → 0.18.1

Sign up to get free protection for your applications and to get access to all the features.
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