bmg 0.18.6 → 0.18.10

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
- 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.