bmg 0.19.1 → 0.19.3

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