bmg 0.18.6 → 0.18.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 482e0bb06c06e4afc620696491cf75f788902240c75f9c82d71ce9b0db4deee1
4
- data.tar.gz: f49609c7cf929149d91bb1c29703fd4e5fb7d861073b38ad916a851214ec20b9
2
+ SHA1:
3
+ metadata.gz: 8b2c8128cd4f8a2dad6d605f518cc9c0c8d40060
4
+ data.tar.gz: 9560e2261e1c0abb410ef9311b003c26fa347f16
5
5
  SHA512:
6
- metadata.gz: a4068309c373faa50bfc4549648ee26e3220187192a529ee8c6c890424c52b4d05103c2ebf7b137354c1c8a82a756eafbdb5c6b266f489c3c6e0a0b9923a4d15
7
- data.tar.gz: ba7a0046919e2d796c58cae326f3d4cf7b86f6d577e34f79395c04286c4bf463cb4c349cb2a5ce4611fbd6e310ebf521736fb190bcc1d1c2c42c51ccc175fb2e
6
+ metadata.gz: 4dbdd828629da347e11df110e15d0970e1f0862825ac0182a670cd0ff5fd8fb5a9e9e4eea11d3a761e264513227eacd5aa7cb9f2f3a609557c93a0ba3fb8b5cc
7
+ data.tar.gz: c5662d96afd0666cbd17d86fb35bf9f21fe1d14e04d63c043e7c7c069fd01bb31f47653a2a1d553fc157fbcbf3a145a865d4552afdfae9248ec32539f8cfcd18
@@ -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
@@ -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
@@ -213,8 +213,7 @@ module Bmg
213
213
  def _collect_joins(sexpr, joins)
214
214
  case sexpr.first
215
215
  when :and
216
- _collect_joins(sexpr[1], joins)
217
- _collect_joins(sexpr[2], joins)
216
+ sexpr[1..-1].each{ |term| _collect_joins(term, joins) }
218
217
  when :eq
219
218
  joins << sexpr
220
219
  else
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
@@ -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'
@@ -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/version.rb CHANGED
@@ -2,7 +2,7 @@ module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 18
5
- TINY = 6
5
+ TINY = 10
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.6
4
+ version: 0.18.10
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-11 00:00:00.000000000 Z
11
+ date: 2021-11-26 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
@@ -261,6 +261,7 @@ files:
261
261
  - lib/bmg/sql/processor/semi_join.rb
262
262
  - lib/bmg/sql/processor/star.rb
263
263
  - lib/bmg/sql/processor/summarize.rb
264
+ - lib/bmg/sql/processor/transform.rb
264
265
  - lib/bmg/sql/processor/where.rb
265
266
  - lib/bmg/sql/relation.rb
266
267
  - lib/bmg/sql/support/from_clause_orderer.rb
@@ -272,10 +273,14 @@ files:
272
273
  - lib/bmg/summarizer/concat.rb
273
274
  - lib/bmg/summarizer/count.rb
274
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
275
279
  - lib/bmg/summarizer/max.rb
276
280
  - lib/bmg/summarizer/min.rb
277
281
  - lib/bmg/summarizer/multiple.rb
278
282
  - lib/bmg/summarizer/percentile.rb
283
+ - lib/bmg/summarizer/positional.rb
279
284
  - lib/bmg/summarizer/stddev.rb
280
285
  - lib/bmg/summarizer/sum.rb
281
286
  - lib/bmg/summarizer/value_by.rb
@@ -312,7 +317,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
312
317
  - !ruby/object:Gem::Version
313
318
  version: '0'
314
319
  requirements: []
315
- rubygems_version: 3.0.8
320
+ rubyforge_project:
321
+ rubygems_version: 2.6.14.4
316
322
  signing_key:
317
323
  specification_version: 4
318
324
  summary: Bmg is Alf's successor.