bmg 0.19.1 → 0.19.3

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
- SHA1:
3
- metadata.gz: 52f3bf703312e48ca61bb2dbc88711be1dde8a89
4
- data.tar.gz: 4f5029df9dc203d7ec82c0da066771ed61fe6182
2
+ SHA256:
3
+ metadata.gz: 773942a08ee8480d73b6d455ef2560c1136002223c86b38ff549beaf280e2760
4
+ data.tar.gz: 22043853a3d11acde72865db0ad870f2fe033a71e9474ffb1451cc4d69a58d30
5
5
  SHA512:
6
- metadata.gz: e9ac87e62f17250706eefb5ce091e3e77f0b91268910ae0a9fa1446eda6adaeed4ef852d022ecab8c9869b5b5da34db678bf7af811c93de08aad1bec28a39b37
7
- data.tar.gz: 7c4117b376c5a406612729c9dfb27b5e22e76ec96e269652bf3a99b53d5c890dfc38c3d269558d08267ca8d4aeaad1c60bad89bc4d24e3b0079a10fc49bff73d
6
+ metadata.gz: 96dc776f81d2b15093372b843e4bb20c4207e0e59a44842130cb79035fd06ab32fb633f34479c2022b7ad5c958ade798fdc10f07cdcf8db60848d382554067eb
7
+ data.tar.gz: fa08f7f03dc62e03302e82e68ba0b32505c1fce2f8a78c498c40894de37e1e486e60104e18a7e84761ea56b947bf6c585825ce2462a702b0e85d30601a138ac5
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # Bmg, a relational algebra (Alf's successor)!
2
2
 
3
- [![Build Status](https://travis-ci.com/enspirit/bmg.svg?branch=master)](https://travis-ci.com/enspirit/bmg)
4
-
5
3
  Bmg is a relational algebra implemented as a ruby library. It implements the
6
4
  [Relation as First-Class Citizen](http://www.try-alf.org/blog/2013-10-21-relations-as-first-class-citizen)
7
5
  paradigm contributed with [Alf](http://www.try-alf.org/) a few years ago.
@@ -210,6 +208,7 @@ r.autowrap(split: '_') # structure a flat relation, split:
210
208
  r.autosummarize([:a, :b, ...], x: :sum) # (experimental) usual summarizers supported
211
209
  r.constants(x: 12, ...) # add constant attributes (sometimes useful in unions)
212
210
  r.extend(x: ->(t){ ... }, ...) # add computed attributes
211
+ r.extend(x: :y) # shortcut for r.extend(x: ->(t){ t[:y] })
213
212
  r.exclude(predicate) # shortcut for restrict(!predicate)
214
213
  r.group([:a, :b, ...], :x) # relation-valued attribute from attributes
215
214
  r.image(right, :x, [:a, :b, ...]) # relation-valued attribute from another relation
@@ -160,7 +160,12 @@ module Bmg
160
160
 
161
161
  def extend_it(tuple)
162
162
  @extension.each_with_object(tuple.dup) { |(k,v), memo|
163
- memo[k] = v.call(tuple)
163
+ memo[k] = case v
164
+ when Symbol
165
+ tuple[v]
166
+ else
167
+ v.call(tuple)
168
+ end
164
169
  memo
165
170
  }
166
171
  end
@@ -39,7 +39,9 @@ module Bmg
39
39
  private
40
40
 
41
41
  def tuple(row)
42
- row.to_hash.each_with_object({}){|(k,v),h| h[k.to_sym] = v }
42
+ row.to_hash.each_with_object({}){|(k,v),h|
43
+ h[k.to_sym] = v
44
+ }
43
45
  end
44
46
 
45
47
  def handle_type(type)
@@ -81,6 +81,7 @@ module Bmg
81
81
  case sexpr.func_name
82
82
  when :cast
83
83
  to_cast = apply(sexpr.func_args.first)
84
+ to_cast = ::Sequel.expr(nil) if to_cast.nil?
84
85
  type = sexpr.func_args.last.last
85
86
  to_cast.cast(type)
86
87
  else
@@ -0,0 +1,33 @@
1
+ module Bmg
2
+ module Sql
3
+ class Processor
4
+ class Extend < Processor
5
+
6
+ def initialize(extension, builder)
7
+ super(builder)
8
+ @extension = extension
9
+ end
10
+ attr_reader :extension
11
+
12
+ def on_set_operator(sexpr)
13
+ apply(builder.from_self(sexpr))
14
+ end
15
+ alias :on_union :on_set_operator
16
+ alias :on_except :on_set_operator
17
+ alias :on_intersect :on_set_operator
18
+
19
+ def on_select_star(sexpr)
20
+ raise NotImplementedError, "Extend on * is not supported"
21
+ end
22
+
23
+ def on_select_list(sexpr)
24
+ sexpr + extension.each_pair.map{|(k,v)|
25
+ desaliased = sexpr.desaliaser[v]
26
+ [:select_item, desaliased, [:column_name, k] ]
27
+ }
28
+ end
29
+
30
+ end # class Extend
31
+ end # class Processor
32
+ end # module Sql
33
+ end # module Bmg
@@ -10,7 +10,7 @@ module Bmg
10
10
 
11
11
  def on_select_list(sexpr)
12
12
  reordered = sexpr.sexpr_body.sort{|i1,i2|
13
- @indexes[i1.as_name] <=> @indexes[i2.as_name]
13
+ @indexes[i1.as_name.to_s] <=> @indexes[i2.as_name.to_s]
14
14
  }
15
15
  reordered.unshift(:select_list)
16
16
  end
@@ -39,9 +39,10 @@ module Bmg
39
39
  def falsy?(sexpr)
40
40
  return false unless sexpr.respond_to?(:predicate)
41
41
  return false if sexpr.predicate.nil?
42
+
42
43
  left = Predicate.new(Predicate::Grammar.sexpr(sexpr.predicate)).unqualify
43
44
  right = Predicate.new(Predicate::Grammar.sexpr(@predicate.sexpr)).unqualify
44
- return (left & right).contradiction?
45
+ (left & right).contradiction?
45
46
  end
46
47
 
47
48
  end # class Where
@@ -71,6 +71,7 @@ require_relative 'processor/distinct'
71
71
  require_relative 'processor/all'
72
72
  require_relative 'processor/clip'
73
73
  require_relative 'processor/constants'
74
+ require_relative 'processor/extend'
74
75
  require_relative 'processor/star'
75
76
  require_relative 'processor/rename'
76
77
  require_relative 'processor/order_by'
@@ -57,6 +57,23 @@ module Bmg
57
57
  _instance(type, builder, expr)
58
58
  end
59
59
 
60
+ def _extend(type, extension)
61
+ supported, unsupported = {}, {}
62
+ extension.each_pair do |k,v|
63
+ (v.is_a?(Symbol) ? supported : unsupported)[k] = v
64
+ end
65
+ if supported.empty?
66
+ Operator::Extend.new(type, self, extension)
67
+ elsif unsupported.empty?
68
+ expr = Processor::Extend.new(supported, builder).call(self.expr)
69
+ _instance(type, builder, expr)
70
+ else
71
+ expr = Processor::Extend.new(supported, builder).call(self.expr)
72
+ operand = _instance(type.allbut(unsupported.keys), builder, expr)
73
+ Operator::Extend.new(type, operand, unsupported)
74
+ end
75
+ end
76
+
60
77
  def _join(type, right, on)
61
78
  if right_expr = extract_compatible_sexpr(right)
62
79
  right_expr = Processor::Requalify.new(builder).call(right_expr)
@@ -2,7 +2,13 @@ module Bmg
2
2
  class Ordering
3
3
 
4
4
  def initialize(attrs)
5
- @attrs = attrs
5
+ @attrs = if attrs.empty?
6
+ []
7
+ elsif attrs.first.is_a?(Symbol)
8
+ attrs.map{|a| [a, :asc] }
9
+ else
10
+ attrs
11
+ end
6
12
  end
7
13
  attr_reader :attrs
8
14
 
@@ -33,5 +39,9 @@ module Bmg
33
39
  0
34
40
  end
35
41
 
42
+ def to_a
43
+ attrs.to_a
44
+ end
45
+
36
46
  end # class Ordering
37
47
  end # module Bmg
@@ -3,6 +3,7 @@ module Bmg
3
3
 
4
4
  DEFAULT_PREFS = {
5
5
  attributes_ordering: nil,
6
+ tuple_ordering: nil,
6
7
  extra_attributes: :after
7
8
  }
8
9
 
@@ -17,6 +18,12 @@ module Bmg
17
18
  new(arg)
18
19
  end
19
20
 
21
+ def tuple_ordering
22
+ return nil unless to = options[:tuple_ordering]
23
+
24
+ @tuple_ordering = Ordering.new(to)
25
+ end
26
+
20
27
  def attributes_ordering
21
28
  options[:attributes_ordering]
22
29
  end
@@ -25,10 +32,29 @@ module Bmg
25
32
  options[:extra_attributes]
26
33
  end
27
34
 
35
+ def grouping_attributes
36
+ options[:grouping_attributes]
37
+ end
38
+
39
+ def erase_redundance_in_group(before, current)
40
+ return [nil, current] unless ga = grouping_attributes
41
+ return [current, current] unless before
42
+
43
+ new_before, new_current = current.dup, current.dup
44
+ ga.each do |attr|
45
+ return [new_before, new_current] unless before[attr] == current[attr]
46
+ new_current[attr] = nil
47
+ end
48
+ [new_before, new_current]
49
+ end
50
+
28
51
  def order_attrlist(attrlist)
29
52
  return attrlist if attributes_ordering.nil?
53
+
30
54
  index = Hash[attributes_ordering.each_with_index.to_a]
31
- attrlist.sort{|a,b|
55
+ base = attrlist
56
+ base = attrlist & attributes_ordering if extra_attributes == :ignored
57
+ base.sort{|a,b|
32
58
  ai, bi = index[a], index[b]
33
59
  if ai && bi
34
60
  ai <=> bi
data/lib/bmg/version.rb CHANGED
@@ -2,7 +2,7 @@ module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 19
5
- TINY = 1
5
+ TINY = 3
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
@@ -16,12 +16,14 @@ module Bmg
16
16
  require 'csv'
17
17
  string_or_io, to_s = string_or_io.nil? ? [StringIO.new, true] : [string_or_io, false]
18
18
  headers, csv = infer_headers(relation.type), nil
19
- relation.each do |tuple|
19
+ previous = nil
20
+ each_tuple(relation) do |tuple,i|
20
21
  if csv.nil?
21
22
  headers = infer_headers(tuple) if headers.nil?
22
23
  csv_opts = csv_options.merge(headers: headers)
23
24
  csv = CSV.new(string_or_io, **csv_opts)
24
25
  end
26
+ previous, tuple = output_preferences.erase_redundance_in_group(previous, tuple)
25
27
  csv << headers.map{|h| tuple[h] }
26
28
  end
27
29
  to_s ? string_or_io.string : string_or_io
@@ -6,11 +6,11 @@ module Bmg
6
6
  DEFAULT_OPTIONS = {
7
7
  }
8
8
 
9
- def initialize(csv_options, output_preferences = nil)
10
- @csv_options = DEFAULT_OPTIONS.merge(csv_options)
9
+ def initialize(xlsx_options, output_preferences = nil)
10
+ @xlsx_options = DEFAULT_OPTIONS.merge(xlsx_options)
11
11
  @output_preferences = OutputPreferences.dress(output_preferences)
12
12
  end
13
- attr_reader :csv_options, :output_preferences
13
+ attr_reader :xlsx_options, :output_preferences
14
14
 
15
15
  def call(relation, path)
16
16
  require 'write_xlsx'
@@ -21,22 +21,24 @@ module Bmg
21
21
  attr_reader :workbook, :worksheet
22
22
 
23
23
  def _call(relation, path)
24
- @workbook = WriteXLSX.new(path)
25
- @worksheet = workbook.add_worksheet
24
+ @workbook = xlsx_options[:workbook] || WriteXLSX.new(path)
25
+ @worksheet = xlsx_options[:worksheet] || workbook.add_worksheet
26
26
 
27
27
  headers = infer_headers(relation.type)
28
- relation.each_with_index do |tuple,i|
28
+ before = nil
29
+ each_tuple(relation) do |tuple,i|
29
30
  headers = infer_headers(tuple) if headers.nil?
30
31
  headers.each_with_index do |h,i|
31
32
  worksheet.write_string(0, i, h)
32
33
  end if i == 0
34
+ before, tuple = output_preferences.erase_redundance_in_group(before, tuple)
33
35
  headers.each_with_index do |h,j|
34
36
  meth, *args = write_pair(tuple[h])
35
37
  worksheet.send(meth, 1+i, j, *args)
36
38
  end
37
39
  end
38
40
 
39
- workbook.close
41
+ workbook.close unless xlsx_options[:workbook]
40
42
  path
41
43
  end
42
44
 
data/lib/bmg/writer.rb CHANGED
@@ -12,6 +12,17 @@ module Bmg
12
12
  attrlist ? output_preferences.order_attrlist(attrlist) : nil
13
13
  end
14
14
 
15
+ def each_tuple(relation, &bl)
16
+ if ordering = output_preferences.tuple_ordering
17
+ relation
18
+ .to_a
19
+ .sort{|t1,t2| ordering.compare_attrs(t1, t2) }
20
+ .each_with_index(&bl)
21
+ else
22
+ relation.each_with_index(&bl)
23
+ end
24
+ end
25
+
15
26
  end # module Writer
16
27
  end # module Bmg
17
28
  require_relative 'writer/csv'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bmg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.1
4
+ version: 0.19.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-21 00:00:00.000000000 Z
11
+ date: 2024-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: predicate
@@ -248,6 +248,7 @@ files:
248
248
  - lib/bmg/sql/processor/clip.rb
249
249
  - lib/bmg/sql/processor/constants.rb
250
250
  - lib/bmg/sql/processor/distinct.rb
251
+ - lib/bmg/sql/processor/extend.rb
251
252
  - lib/bmg/sql/processor/flatten.rb
252
253
  - lib/bmg/sql/processor/from_self.rb
253
254
  - lib/bmg/sql/processor/join.rb
@@ -317,8 +318,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
317
318
  - !ruby/object:Gem::Version
318
319
  version: '0'
319
320
  requirements: []
320
- rubyforge_project:
321
- rubygems_version: 2.6.14.4
321
+ rubygems_version: 3.1.6
322
322
  signing_key:
323
323
  specification_version: 4
324
324
  summary: Bmg is Alf's successor.