bmg 0.18.5 → 0.18.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6569e9038c83dda887da734d97ef58f9ff6e94984c78e8eae993561cde3bbfcc
4
- data.tar.gz: 85d65ca717992a132e94ae17dbdf2cc671a161c495eb5be240c3d4e342e90c3f
3
+ metadata.gz: e287adcc94d2a0a5a88604e90cb34895ff801b137478c3cfa8b32d2093e7289c
4
+ data.tar.gz: 148775b4bdfbcb45099ed2587268243eef46b8a214d9bbf5f9f3abc9960764f2
5
5
  SHA512:
6
- metadata.gz: fbeb5215e5942626dd5115d95c091602daa57128014bc0833c38c3460697ef9dbc59b4ccd7566d4d3604e8d7a8c5e7e4812159a5bb89d3dad477e6a7b75545d8
7
- data.tar.gz: 9e8a9eaaf3699d28890cb15faef5b12360f9bf26c5bb0e9bf42ccedba0b7b5a3c8c2eb627f8b1a4e154c8d992426f47ed82153e9f110634f0ba6aa7ceddd1353
6
+ metadata.gz: ae86583f677f487bb3b6e71fa1a5ed07cc731bd927cf74716d68818d330809f86aaddb98c8a8c9848f68c3b0a8ba183996029ecf745db377d740b1c4da59b995
7
+ data.tar.gz: a0fca7d1f3fa2ecc8097a0167d5ec9767a047b61af54212b16030dfe23a46c05e3597641c0c125c40f0652fbdc15d593e7201cc9b430467e90216af0630e4c5d
data/README.md CHANGED
@@ -234,7 +234,11 @@ t.transform(:to_s) # all-attrs transformation
234
234
  t.transform(&:to_s) # similar, but Proc-driven
235
235
  t.transform(:foo => :upcase, ...) # specific-attrs tranformation
236
236
  t.transform([:to_s, :upcase]) # chain-transformation
237
+ r.ungroup([:a, :b, ...]) # ungroup relation-valued attributes within parent tuple
238
+ r.ungroup(:a) # shortcut over ungroup([:a])
237
239
  r.union(right) # relational union
240
+ r.unwrap([:a, :b, ...]) # merge tuple-valued attributes within parent tuple
241
+ r.unwrap(:a) # shortcut over unwrap([:a])
238
242
  r.where(predicate) # alias for restrict(predicate)
239
243
  ```
240
244
 
@@ -69,6 +69,14 @@ module Bmg
69
69
  self.not_matching(right.rename(renaming), on.keys)
70
70
  end
71
71
 
72
+ def ungroup(attr)
73
+ super(attr.is_a?(Symbol) ? [attr] : attr)
74
+ end
75
+
76
+ def unwrap(attr)
77
+ super(attr.is_a?(Symbol) ? [attr] : attr)
78
+ end
79
+
72
80
  end # module Shortcuts
73
81
  end # module Algebra
74
82
  end # module Bmg
data/lib/bmg/algebra.rb CHANGED
@@ -183,6 +183,15 @@ module Bmg
183
183
  end
184
184
  protected :_transform
185
185
 
186
+ def ungroup(attrs)
187
+ _ungroup self.type.ungroup(attrs), attrs
188
+ end
189
+
190
+ def _ungroup(type, attrs)
191
+ Operator::Ungroup.new(type, self, attrs)
192
+ end
193
+ protected :_ungroup
194
+
186
195
  def union(other, options = {})
187
196
  return self if other.is_a?(Relation::Empty)
188
197
  _union self.type.union(other.type), other, options
@@ -193,6 +202,15 @@ module Bmg
193
202
  end
194
203
  protected :_union
195
204
 
205
+ def unwrap(attrs)
206
+ _unwrap self.type.unwrap(attrs), attrs
207
+ end
208
+
209
+ def _unwrap(type, attrs)
210
+ Operator::Unwrap.new(type, self, attrs)
211
+ end
212
+ protected :_unwrap
213
+
196
214
  def spied(spy)
197
215
  return self if spy.nil?
198
216
  Relation::Spied.new(self, spy)
@@ -22,6 +22,7 @@ module Bmg
22
22
 
23
23
  DEFAULT_OPTIONS = {
24
24
  :postprocessor => :none,
25
+ :postprocessor_condition => :all,
25
26
  :split => "_"
26
27
  }
27
28
 
@@ -167,7 +168,7 @@ module Bmg
167
168
 
168
169
  def normalize_options(options)
169
170
  opts = DEFAULT_OPTIONS.merge(options)
170
- opts[:postprocessor] = NoLeftJoinNoise.new(opts[:postprocessor])
171
+ opts[:postprocessor] = NoLeftJoinNoise.new(opts[:postprocessor], opts[:postprocessor_condition])
171
172
  opts
172
173
  end
173
174
 
@@ -209,17 +210,17 @@ module Bmg
209
210
  class NoLeftJoinNoise
210
211
 
211
212
  REMOVERS = {
212
- nil: ->(t,k){ t[k] = nil },
213
- delete: ->(t,k){ t.delete(k) },
214
- none: ->(t,k){ t }
213
+ nil: ->(t,k){ t[k] = nil },
214
+ delete: ->(t,k){ t.delete(k) },
215
+ none: ->(t,k){ t }
215
216
  }
216
217
 
217
- def self.new(remover)
218
+ def self.new(remover, remover_condition = :all)
218
219
  return remover if remover.is_a?(NoLeftJoinNoise)
219
220
  super
220
221
  end
221
222
 
222
- def initialize(remover)
223
+ def initialize(remover, remover_condition = :all)
223
224
  @remover_to_s = remover
224
225
  @remover = case remover
225
226
  when NilClass then REMOVERS[:none]
@@ -229,22 +230,35 @@ module Bmg
229
230
  else
230
231
  raise "Invalid remover `#{remover}`"
231
232
  end
233
+ @remover_condition = case remover_condition
234
+ when :all then ->(tuple){ all_nil?(tuple) }
235
+ when :id then ->(tuple){ id_nil?(tuple) }
236
+ else
237
+ raise "Invalid remover condition `#{remover_condition}`"
238
+ end
232
239
  end
233
240
  attr_reader :remover
234
241
 
235
242
  def call(tuple)
236
243
  tuple.each_key do |k|
237
244
  call(tuple[k]) if tuple[k].is_a?(Hash)
238
- @remover.call(tuple, k) if tuple[k].is_a?(Hash) && all_nil?(tuple[k])
245
+ @remover.call(tuple, k) if tuple[k].is_a?(Hash) && @remover_condition.call(tuple[k])
239
246
  end
240
247
  tuple
241
248
  end
242
249
 
243
250
  def all_nil?(tuple)
244
251
  return false unless tuple.is_a?(Hash)
252
+
245
253
  tuple.all?{|(k,v)| v.nil? || all_nil?(tuple[k]) }
246
254
  end
247
255
 
256
+ def id_nil?(tuple)
257
+ return false unless tuple.is_a?(Hash)
258
+
259
+ tuple[:id].nil?
260
+ end
261
+
248
262
  def inspect
249
263
  @remover_to_s.inspect
250
264
  end
@@ -26,7 +26,11 @@ module Bmg
26
26
  # resulting operabds. This option only applies when (optimized) `on`
27
27
  # contains one attribute only. ; it fallbacks on :index_right
28
28
  # otherwise.
29
- strategy: :refilter_right
29
+ strategy: :refilter_right,
30
+
31
+ # Whether the attributes on which the join is made should be kept
32
+ # in the result or not
33
+ preserve: false
30
34
 
31
35
  }
32
36
 
@@ -96,9 +100,10 @@ module Bmg
96
100
 
97
101
  def build_right_index(right)
98
102
  index = Hash.new{|h,k| h[k] = empty_image }
103
+ butlist = options[:preserve] ? [] : on
99
104
  right.each_with_object(index) do |t, index|
100
105
  key = tuple_project(t, on)
101
- index[key].operand << tuple_image(t, on)
106
+ index[key].operand << tuple_allbut(t, butlist)
102
107
  end
103
108
  if opt = options[:array]
104
109
  sorter = to_sorter(opt)
@@ -249,8 +254,8 @@ module Bmg
249
254
  TupleAlgebra.project(tuple, on)
250
255
  end
251
256
 
252
- def tuple_image(tuple, on)
253
- TupleAlgebra.allbut(tuple, on)
257
+ def tuple_allbut(tuple, butlist)
258
+ TupleAlgebra.allbut(tuple, butlist)
254
259
  end
255
260
 
256
261
  def image_type
@@ -0,0 +1,61 @@
1
+ module Bmg
2
+ module Operator
3
+ class Ungroup
4
+ include Operator::Unary
5
+
6
+ def initialize(type, operand, attrs)
7
+ @type = type
8
+ @operand = operand
9
+ @attrs = attrs
10
+ end
11
+
12
+ protected
13
+
14
+ attr_reader :attrs
15
+
16
+ public
17
+
18
+ def each(&bl)
19
+ return to_enum unless block_given?
20
+ if type.knows_keys? && type.keys.any?{|k| (k & attrs).empty? }
21
+ operand.each do |tuple|
22
+ _each(tuple, attrs[0], attrs[1..-1], &bl)
23
+ end
24
+ else
25
+ with_dups = []
26
+ operand.each do |tuple|
27
+ _each(tuple, attrs[0], attrs[1..-1]){|t|
28
+ with_dups << t
29
+ }
30
+ end
31
+ with_dups.uniq.each(&bl)
32
+ end
33
+ end
34
+
35
+ def _each(tuple, attr, attrs, &bl)
36
+ rva = tuple[attr] || []
37
+ rva.each do |rvt|
38
+ t = tuple.merge(rvt).tap{|t| t.delete(attr) }
39
+ if attrs.empty?
40
+ yield(t)
41
+ else
42
+ _each(t, attrs[0], attrs[1..-1], &bl)
43
+ end
44
+ end
45
+ end
46
+
47
+ def to_ast
48
+ [ :ungroup, operand.to_ast, attrs ]
49
+ end
50
+
51
+ protected
52
+
53
+ protected ### inspect
54
+
55
+ def args
56
+ [ attrs ]
57
+ end
58
+
59
+ end # class Ungroup
60
+ end # module Operator
61
+ end # module Bmg
@@ -0,0 +1,47 @@
1
+ module Bmg
2
+ module Operator
3
+ class Unwrap
4
+ include Operator::Unary
5
+
6
+ def initialize(type, operand, attrs)
7
+ @type = type
8
+ @operand = operand
9
+ @attrs = attrs
10
+ end
11
+
12
+ protected
13
+
14
+ attr_reader :attrs
15
+
16
+ public
17
+
18
+ def each(&bl)
19
+ return to_enum unless block_given?
20
+ operand.each do |tuple|
21
+ yield tuple_unwrap(tuple)
22
+ end
23
+ end
24
+
25
+ def to_ast
26
+ [ :unwrap, operand.to_ast, attrs ]
27
+ end
28
+
29
+ protected
30
+
31
+ def tuple_unwrap(tuple)
32
+ attrs.inject(tuple.dup){|t,attr|
33
+ t.merge(tuple[attr]).tap{|t2|
34
+ t2.delete(attr)
35
+ }
36
+ }
37
+ end
38
+
39
+ protected ### inspect
40
+
41
+ def args
42
+ [ attrs ]
43
+ end
44
+
45
+ end # class Unwrap
46
+ end # module Operator
47
+ end # module Bmg
data/lib/bmg/operator.rb CHANGED
@@ -47,4 +47,6 @@ require_relative 'operator/restrict'
47
47
  require_relative 'operator/rxmatch'
48
48
  require_relative 'operator/summarize'
49
49
  require_relative 'operator/transform'
50
+ require_relative 'operator/ungroup'
50
51
  require_relative 'operator/union'
52
+ require_relative 'operator/unwrap'
@@ -4,6 +4,7 @@ module Bmg
4
4
  include Reader
5
5
 
6
6
  DEFAULT_OPTIONS = {
7
+ sheet: 0,
7
8
  skip: 0,
8
9
  row_num: true
9
10
  }
@@ -19,7 +20,7 @@ module Bmg
19
20
  require 'roo'
20
21
  xlsx = Roo::Spreadsheet.open(@path, @options)
21
22
  headers = nil
22
- xlsx.sheet(0)
23
+ xlsx.sheet(@options[:sheet])
23
24
  .each
24
25
  .drop(@options[:skip])
25
26
  .each_with_index
@@ -78,16 +78,29 @@ module Bmg
78
78
  end
79
79
 
80
80
  def on_func_call(sexpr)
81
- args = sexpr.func_args.map{|fa| apply(fa) }
82
- ::Sequel.function(sexpr.func_name, *args)
81
+ case sexpr.func_name
82
+ when :cast
83
+ to_cast = apply(sexpr.func_args.first)
84
+ type = sexpr.func_args.last.last
85
+ to_cast.cast(type)
86
+ else
87
+ args = sexpr.func_args.map{|fa| apply(fa) }
88
+ ::Sequel.function(sexpr.func_name, *args)
89
+ end
83
90
  end
84
91
 
85
92
  def on_summarizer(sexpr)
86
- if sexpr.summary_expr
87
- ::Sequel.function(sexpr.summary_func, apply(sexpr.summary_expr))
93
+ func, distinct = if sexpr.summary_func == :distinct_count
94
+ [:count, true]
95
+ else
96
+ [sexpr.summary_func, false]
97
+ end
98
+ f = if sexpr.summary_expr
99
+ ::Sequel.function(func, apply(sexpr.summary_expr))
88
100
  else
89
- ::Sequel.function(sexpr.summary_func).*
101
+ ::Sequel.function(func).*
90
102
  end
103
+ distinct ? f.distinct : f
91
104
  end
92
105
 
93
106
  def on_qualified_name(sexpr)
@@ -36,10 +36,10 @@ module Bmg
36
36
  [:select_item,
37
37
  [ :summarizer,
38
38
  summarizer.to_summarizer_name,
39
- sexpr.desaliaser[attr] ],
39
+ sexpr.desaliaser[summarizer.functor] ],
40
40
  [:column_name, attr.to_s] ]
41
41
  }
42
- [:select_list] + by_list + group_list
42
+ ([:select_list] + by_list + group_list)
43
43
  end
44
44
 
45
45
  end # class Summarize
@@ -0,0 +1,105 @@
1
+ module Bmg
2
+ module Sql
3
+ class Processor
4
+ class Transform < Processor
5
+
6
+ module SplitSupported
7
+ extend(self)
8
+
9
+ def split_supported(tr, &bl)
10
+ case tr
11
+ when Array
12
+ i = tr.find_index{|x| !bl.call(x) } || tr.size
13
+ [tr[0...i], tr[i..-1]].map{|a|
14
+ case a.size
15
+ when 0 then nil
16
+ when 1 then a.first
17
+ else a
18
+ end
19
+ }
20
+ when Hash
21
+ tr.inject([{}, {}]){|(sup,unsup),(k,v)|
22
+ mine, hers = _split_supported(v, &bl)
23
+ [
24
+ sup.merge(k => mine),
25
+ unsup.merge(k => hers)
26
+ ].map(&:compact)
27
+ }.map{|h| h.empty? ? nil : h }
28
+ else
29
+ _split_supported(tr, &bl)
30
+ end
31
+ end
32
+
33
+ def _split_supported(tr, &bl)
34
+ if tr.is_a?(Array)
35
+ split_supported(tr, &bl)
36
+ else
37
+ bl.call(tr) ? [tr, nil] : [nil, tr]
38
+ end
39
+ end
40
+ end # module SplitSupported
41
+
42
+ def initialize(transformation, options, builder)
43
+ raise NotSupportedError unless options.empty?
44
+ super(builder)
45
+ @transformation = transformation
46
+ end
47
+ attr_reader :transformation
48
+
49
+ def self.split_supported(*args, &bl)
50
+ SplitSupported.split_supported(*args, &bl)
51
+ end
52
+
53
+ def on_select_list(sexpr)
54
+ sexpr.each_with_index.map{|child,index|
55
+ index == 0 ? child : apply(child)
56
+ }
57
+ end
58
+
59
+ def on_select_item(sexpr)
60
+ as = sexpr.as_name.to_sym
61
+ case t = transformation_for(as)
62
+ when NilClass
63
+ sexpr
64
+ when Class, Array
65
+ sexpr([:select_item,
66
+ func_call_node(sexpr, Array(t).reverse),
67
+ sexpr[2]
68
+ ])
69
+ else
70
+ raise NotSupportedError
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def func_call_node(sexpr, ts)
77
+ _func_call_node(sexpr, ts.first, ts[1..-1])
78
+ end
79
+
80
+ def _func_call_node(sexpr, head, tail)
81
+ inside = if tail.empty?
82
+ sexpr[1]
83
+ else
84
+ _func_call_node(sexpr, tail.first, tail[1..-1])
85
+ end
86
+ [:func_call,
87
+ :cast,
88
+ inside,
89
+ [ :literal, head ] ]
90
+ end
91
+
92
+ def transformation_for(as)
93
+ case t = transformation
94
+ when Class then t
95
+ when Hash then t[as]
96
+ when Array then t
97
+ else
98
+ raise Sql::NotSupportedError, "Unable to use `#{as}` for `transform`"
99
+ end
100
+ end
101
+
102
+ end # class Transform
103
+ end # class Processor
104
+ end # module Sql
105
+ end # module Bmg
@@ -85,4 +85,5 @@ require_relative 'processor/semi_join'
85
85
  require_relative 'processor/flatten'
86
86
  require_relative 'processor/requalify'
87
87
  require_relative 'processor/summarize'
88
+ require_relative 'processor/transform'
88
89
  require_relative 'processor/bind'
@@ -123,13 +123,13 @@ module Bmg
123
123
 
124
124
  def _rename(type, renaming)
125
125
  expr = before_use(self.expr)
126
- expr = Processor::Rename.new(renaming, builder).call(self.expr)
126
+ expr = Processor::Rename.new(renaming, builder).call(expr)
127
127
  _instance(type, builder, expr)
128
128
  end
129
129
 
130
130
  def _restrict(type, predicate)
131
131
  expr = before_use(self.expr)
132
- expr = Processor::Where.new(predicate, builder).call(self.expr)
132
+ expr = Processor::Where.new(predicate, builder).call(expr)
133
133
  _instance(type, builder, expr)
134
134
  end
135
135
 
@@ -137,23 +137,37 @@ module Bmg
137
137
  summarization = ::Bmg::Summarizer.summarization(defs)
138
138
  if can_compile_summarization?(summarization)
139
139
  expr = before_use(self.expr)
140
- expr = Processor::Summarize.new(by, summarization, builder).call(self.expr)
140
+ expr = Processor::Summarize.new(by, summarization, builder).call(expr)
141
141
  _instance(type, builder, expr)
142
142
  else
143
143
  super
144
144
  end
145
145
  end
146
146
 
147
+ def _transform(type, transformation, options)
148
+ expr = before_use(self.expr)
149
+ sup, unsup = Processor::Transform.split_supported(transformation){|x|
150
+ [String, Integer, Float, Date, DateTime].include?(x)
151
+ }
152
+ return super if sup.nil?
153
+ expr = Processor::Transform.new(sup, options, builder).call(expr)
154
+ result = _instance(type, builder, expr)
155
+ result = result.transform(unsup, options) if unsup
156
+ result
157
+ rescue Sql::NotSupportedError
158
+ super
159
+ end
160
+
147
161
  def can_compile_summarization?(summarization)
148
162
  summarization.values.all?{|s|
149
- [:avg, :count, :max, :min, :sum].include?(s.to_summarizer_name)
163
+ [:avg, :count, :max, :min, :sum, :distinct_count].include?(s.to_summarizer_name)
150
164
  }
151
165
  end
152
166
 
153
167
  def _union(type, right, options)
154
168
  if right_expr = extract_compatible_sexpr(right)
155
169
  expr = before_use(self.expr)
156
- expr = Processor::Merge.new(:union, !!options[:all], right_expr, builder).call(self.expr)
170
+ expr = Processor::Merge.new(:union, !!options[:all], right_expr, builder).call(expr)
157
171
  _instance(type, builder, expr)
158
172
  else
159
173
  super
data/lib/bmg/sql.rb CHANGED
@@ -2,7 +2,10 @@ require 'sexpr'
2
2
  module Bmg
3
3
 
4
4
  module Sql
5
- end
5
+
6
+ class NotSupportedError < Bmg::Error; end
7
+
8
+ end # module Sql
6
9
 
7
10
  def sql(table, type = Type::ANY)
8
11
  builder = Sql::Builder.new
@@ -0,0 +1,36 @@
1
+ module Bmg
2
+ class Summarizer
3
+ #
4
+ # Collect the count of distinct values.
5
+ #
6
+ # Example:
7
+ #
8
+ # # direct ruby usage
9
+ # Bmg::Summarizer.distinct_count(:qty).summarize(...)
10
+ #
11
+ class DistinctCount < Summarizer
12
+
13
+ # Returns [] as least value.
14
+ def least()
15
+ {}
16
+ end
17
+
18
+ # Adds val to the memo array
19
+ def _happens(memo, val)
20
+ memo[val] = true
21
+ memo
22
+ end
23
+
24
+ def finalize(memo)
25
+ memo.keys.size
26
+ end
27
+
28
+ end # class DistinctCount
29
+
30
+ # Factors a distinct count summarizer
31
+ def self.distinct_count(*args, &bl)
32
+ DistinctCount.new(*args, &bl)
33
+ end
34
+
35
+ end # class Summarizer
36
+ end # module Bmg
@@ -0,0 +1,25 @@
1
+ module Bmg
2
+ class Summarizer
3
+ #
4
+ # First summarizer.
5
+ #
6
+ # Example:
7
+ #
8
+ # # direct ruby usage
9
+ # Bmg::Summarizer.first(:qty, :order => [:id]).summarize(...)
10
+ #
11
+ class First < Positional
12
+
13
+ def choose(t1, t2)
14
+ t1
15
+ end
16
+
17
+ end # class First
18
+
19
+ # Factors a first summarizer
20
+ def self.first(*args, &bl)
21
+ First.new(*args, &bl)
22
+ end
23
+
24
+ end # class Summarizer
25
+ end # module Bmg
@@ -0,0 +1,25 @@
1
+ module Bmg
2
+ class Summarizer
3
+ #
4
+ # Last summarizer.
5
+ #
6
+ # Example:
7
+ #
8
+ # # direct ruby usage
9
+ # Bmg::Summarizer.last(:qty, :order => [:id]).summarize(...)
10
+ #
11
+ class Last < Positional
12
+
13
+ def choose(t1, t2)
14
+ t2
15
+ end
16
+
17
+ end # class Last
18
+
19
+ # Factors a last summarizer
20
+ def self.last(*args, &bl)
21
+ Last.new(*args, &bl)
22
+ end
23
+
24
+ end # class Summarizer
25
+ end # module Bmg
@@ -0,0 +1,33 @@
1
+ module Bmg
2
+ class Summarizer
3
+ # See First and Last
4
+ class Positional < Summarizer
5
+
6
+ def initialize(*args, &block)
7
+ super
8
+ raise ArgumentError, "Missing order" unless options[:order]
9
+ @ordering = Ordering.new(options[:order])
10
+ end
11
+ attr_reader :ordering
12
+
13
+ def least
14
+ nil
15
+ end
16
+
17
+ def happens(memo, tuple)
18
+ if memo.nil?
19
+ tuple
20
+ else
21
+ c = ordering.call(memo, tuple)
22
+ c <= 0 ? choose(memo, tuple) : choose(tuple, memo)
23
+ end
24
+ end
25
+
26
+ def finalize(memo)
27
+ return nil if memo.nil?
28
+ extract_value(memo)
29
+ end
30
+
31
+ end # class Positional
32
+ end # class Summarizer
33
+ end # module Bmg
@@ -39,7 +39,7 @@ module Bmg
39
39
  def default_tuple
40
40
  (options[:series] || []).each_with_object({}){|s,ss|
41
41
  s_def = options[:default]
42
- s_def = s_def.to_sym if s_def && options[:symbolize]
42
+ s = s.to_sym if s && options[:symbolize]
43
43
  ss[s] = s_def
44
44
  }
45
45
  end
@@ -131,7 +131,10 @@ module Bmg
131
131
 
132
132
  # Returns the canonical summarizer name
133
133
  def to_summarizer_name
134
- self.class.name.downcase[/::([a-z]+)$/, 1].to_sym
134
+ self.class.name
135
+ .gsub(/[a-z][A-Z]/){|x| x.split('').join('_') }
136
+ .downcase[/::([a-z_]+)$/, 1]
137
+ .to_sym
135
138
  end
136
139
 
137
140
  protected
@@ -161,7 +164,11 @@ require_relative 'summarizer/stddev'
161
164
  require_relative 'summarizer/percentile'
162
165
  require_relative 'summarizer/collect'
163
166
  require_relative 'summarizer/distinct'
167
+ require_relative 'summarizer/distinct_count'
164
168
  require_relative 'summarizer/concat'
165
169
  require_relative 'summarizer/by_proc'
166
170
  require_relative 'summarizer/multiple'
167
171
  require_relative 'summarizer/value_by'
172
+ require_relative 'summarizer/positional'
173
+ require_relative 'summarizer/first'
174
+ require_relative 'summarizer/last'
@@ -69,6 +69,11 @@ module Bmg
69
69
  Keys.new(shared, false)
70
70
  end
71
71
 
72
+ def unwrap(oldtype, newtype, attrs)
73
+ untouched = @keys.select{|k| (attrs & k).empty? }
74
+ Keys.new(untouched, false)
75
+ end
76
+
72
77
  public ## usuals
73
78
 
74
79
  def to_a
@@ -6,8 +6,12 @@ module Bmg
6
6
  end
7
7
  attr_reader :attrs
8
8
 
9
+ def call(t1, t2)
10
+ comparator.call(t1, t2)
11
+ end
12
+
9
13
  def comparator
10
- ->(t1, t2) {
14
+ @comparator ||= ->(t1, t2) {
11
15
  attrs.each do |(attr,direction)|
12
16
  c = t1[attr] <=> t2[attr]
13
17
  return (direction == :desc ? -c : c) unless c==0
@@ -59,6 +59,19 @@ module Bmg
59
59
  when Regexp
60
60
  m = with.match(value.to_s)
61
61
  m.nil? ? m : m.to_s
62
+ when Class
63
+ return value if value.nil?
64
+ if with.respond_to?(:parse)
65
+ with.parse(value)
66
+ elsif with == Integer
67
+ Integer(value)
68
+ elsif with == Float
69
+ Float(value)
70
+ elsif with == String
71
+ value.to_s
72
+ else
73
+ raise ArgumentError, "#{with} should respond to `parse`"
74
+ end
62
75
  when Proc
63
76
  with.call(value)
64
77
  when Hash
data/lib/bmg/type.rb CHANGED
@@ -82,7 +82,7 @@ module Bmg
82
82
 
83
83
  def with_keys(keys)
84
84
  dup.tap{|x|
85
- x.keys = Keys.new(keys)
85
+ x.keys = keys ? Keys.new(keys) : nil
86
86
  }
87
87
  end
88
88
 
@@ -266,6 +266,15 @@ module Bmg
266
266
  }
267
267
  end
268
268
 
269
+ def ungroup(attrlist)
270
+ known_attributes!(attrlist) if typechecked? && knows_attrlist?
271
+ dup.tap{|x|
272
+ x.attrlist = nil
273
+ x.predicate = Predicate.tautology
274
+ x.keys = nil
275
+ }
276
+ end
277
+
269
278
  def union(other)
270
279
  if typechecked? && knows_attrlist? && other.knows_attrlist?
271
280
  missing = self.attrlist - other.attrlist
@@ -280,6 +289,15 @@ module Bmg
280
289
  }
281
290
  end
282
291
 
292
+ def unwrap(attrlist)
293
+ known_attributes!(attrlist) if typechecked? && knows_attrlist?
294
+ dup.tap{|x|
295
+ x.attrlist = nil
296
+ x.predicate = predicate.and_split(attrlist).last
297
+ x.keys = self._keys.unwrap(self, x, attrlist) if knows_keys?
298
+ }
299
+ end
300
+
283
301
  private
284
302
 
285
303
  def known_attributes!(attrs)
data/lib/bmg/version.rb CHANGED
@@ -2,7 +2,7 @@ module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 18
5
- TINY = 5
5
+ TINY = 9
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
metadata CHANGED
@@ -1,35 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bmg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.5
4
+ version: 0.18.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-08 00:00:00.000000000 Z
11
+ date: 2021-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: predicate
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 2.5.0
20
17
  - - "~>"
21
18
  - !ruby/object:Gem::Version
22
19
  version: '2.5'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.5.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 2.5.0
30
27
  - - "~>"
31
28
  - !ruby/object:Gem::Version
32
29
  version: '2.5'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.5.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: path
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -164,7 +164,9 @@ files:
164
164
  - lib/bmg/operator/shared/unary.rb
165
165
  - lib/bmg/operator/summarize.rb
166
166
  - lib/bmg/operator/transform.rb
167
+ - lib/bmg/operator/ungroup.rb
167
168
  - lib/bmg/operator/union.rb
169
+ - lib/bmg/operator/unwrap.rb
168
170
  - lib/bmg/reader.rb
169
171
  - lib/bmg/reader/csv.rb
170
172
  - lib/bmg/reader/excel.rb
@@ -259,6 +261,7 @@ files:
259
261
  - lib/bmg/sql/processor/semi_join.rb
260
262
  - lib/bmg/sql/processor/star.rb
261
263
  - lib/bmg/sql/processor/summarize.rb
264
+ - lib/bmg/sql/processor/transform.rb
262
265
  - lib/bmg/sql/processor/where.rb
263
266
  - lib/bmg/sql/relation.rb
264
267
  - lib/bmg/sql/support/from_clause_orderer.rb
@@ -270,10 +273,14 @@ files:
270
273
  - lib/bmg/summarizer/concat.rb
271
274
  - lib/bmg/summarizer/count.rb
272
275
  - lib/bmg/summarizer/distinct.rb
276
+ - lib/bmg/summarizer/distinct_count.rb
277
+ - lib/bmg/summarizer/first.rb
278
+ - lib/bmg/summarizer/last.rb
273
279
  - lib/bmg/summarizer/max.rb
274
280
  - lib/bmg/summarizer/min.rb
275
281
  - lib/bmg/summarizer/multiple.rb
276
282
  - lib/bmg/summarizer/percentile.rb
283
+ - lib/bmg/summarizer/positional.rb
277
284
  - lib/bmg/summarizer/stddev.rb
278
285
  - lib/bmg/summarizer/sum.rb
279
286
  - lib/bmg/summarizer/value_by.rb
@@ -310,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
310
317
  - !ruby/object:Gem::Version
311
318
  version: '0'
312
319
  requirements: []
313
- rubygems_version: 3.0.8
320
+ rubygems_version: 3.1.4
314
321
  signing_key:
315
322
  specification_version: 4
316
323
  summary: Bmg is Alf's successor.