bmg 0.21.5 → 0.22.0

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
2
  SHA1:
3
- metadata.gz: 91f0681124b487847c3af44ea65635ebb02df727
4
- data.tar.gz: e7613c53a87c753c3a8a8dfb5876de592219b0c3
3
+ metadata.gz: be420623e04afe03570a1756d1607a202eb96481
4
+ data.tar.gz: 150b4f3f4daced5825e661765ed4530455263422
5
5
  SHA512:
6
- metadata.gz: 5948cf2f15c306b95cd289c01ea84602c250655a75de6bd884289efa4c1cb3535acbd811d77166750006f405a2d294a50135bc1eda92f390746aa0ae754c87d8
7
- data.tar.gz: 9b82273935815d8808321d08dc292d3e4b3bb81a6eb97ef757715e4d7b0f6ff511d6ea648cb9851dee767cb29bb27559611a31494ad3fdcddbd8298fdcfa40e2
6
+ metadata.gz: 5b2d86803d1bfedfa5605f158a34c744e8b0fa77ed25314e38a28dac6bdef1512bc51a3398cfcd55df42fb60354880b4378b49d339682724cd3da42ef282ef27
7
+ data.tar.gz: 7321a2b290d2852ff4e2fe52b915e190d3fb13143807e8ac062928c8745a330d70c43fcbbf4abb545507cc8d2b9a05167502c44c7b6d5310c24b2e781920f827
data/README.md CHANGED
@@ -260,6 +260,7 @@ r.left_join(right, [:a, :b, ...], {...}) # left join with optional default r
260
260
  r.left_join(right, {:a => :x, ...}, {...}) # left join after right reversed renaming
261
261
  r.matching(right, [:a, :b, ...]) # semi join, aka where exists
262
262
  r.matching(right, :a => :x, :b => :y, ...) # semi join, after right reversed renaming
263
+ r.minus(right) # set difference
263
264
  r.not_matching(right, [:a, :b, ...]) # inverse semi join, aka where not exists
264
265
  r.not_matching(right, :a => :x, ...) # inverse semi join, after right reversed renaming
265
266
  r.page([[:a, :asc], ...], 12, page_size: 10) # paging, using an explicit ordering
@@ -276,7 +277,7 @@ r.transform(:foo => :upcase, ...) # specific-attrs tranformation
276
277
  r.transform([:to_s, :upcase]) # chain-transformation
277
278
  r.ungroup([:a, :b, ...]) # ungroup relation-valued attributes within parent tuple
278
279
  r.ungroup(:a) # shortcut over ungroup([:a])
279
- r.union(right) # relational union
280
+ r.union(right) # set union
280
281
  r.unwrap([:a, :b, ...]) # merge tuple-valued attributes within parent tuple
281
282
  r.unwrap(:a) # shortcut over unwrap([:a])
282
283
  r.where(predicate) # alias for restrict(predicate)
data/lib/bmg/algebra.rb CHANGED
@@ -203,6 +203,16 @@ module Bmg
203
203
  end
204
204
  protected :_union
205
205
 
206
+ def minus(other)
207
+ return self if other.is_a?(Relation::Empty)
208
+ _minus self.type.minus(other.type), other
209
+ end
210
+
211
+ def _minus(type, other)
212
+ Operator::Minus.new(type, [self, other])
213
+ end
214
+ protected :_minus
215
+
206
216
  def unwrap(attrs)
207
217
  _unwrap self.type.unwrap(attrs), attrs
208
218
  end
data/lib/bmg/error.rb CHANGED
@@ -19,4 +19,8 @@ module Bmg
19
19
  # Raised when an operator is badly used
20
20
  class MisuseError < Error; end
21
21
 
22
+ # Raised when some compilation is not supported, as an indicator
23
+ # to backtrack to something more ruby-native.
24
+ class NotSupportedError < Error; end
25
+
22
26
  end
@@ -131,7 +131,7 @@ module Bmg
131
131
  else
132
132
  super
133
133
  end
134
- rescue UnknownAttributesError
134
+ rescue UnsupportedError, UnknownAttributesError
135
135
  super
136
136
  end
137
137
 
@@ -64,7 +64,7 @@ module Bmg
64
64
  protected ### optimization
65
65
 
66
66
  def _page(type, ordering, page_index, options)
67
- attrs = ordering.map{|(k,v)| k}
67
+ attrs = ordering.map{|(k,v)| k }
68
68
  cs_attrs = the_constants.keys
69
69
  if (attrs & cs_attrs).empty?
70
70
  operand
@@ -73,6 +73,8 @@ module Bmg
73
73
  else
74
74
  super
75
75
  end
76
+ rescue UnsupportedError
77
+ super
76
78
  end
77
79
 
78
80
  def _restrict(type, predicate)
@@ -133,6 +133,8 @@ module Bmg
133
133
  else
134
134
  super
135
135
  end
136
+ rescue UnsupportedError
137
+ super
136
138
  end
137
139
 
138
140
  def _project(type, attrlist)
@@ -195,6 +195,8 @@ module Bmg
195
195
  .page(ordering, page_index, opts)
196
196
  .image(right, as, on, options)
197
197
  end
198
+ rescue UnsupportedError
199
+ super
198
200
  end
199
201
 
200
202
  def _project(type, attrlist)
@@ -0,0 +1,49 @@
1
+ module Bmg
2
+ module Operator
3
+ #
4
+ # Minus operator.
5
+ #
6
+ # Returns all tuples which are in the left operand but not in the right
7
+ # operand.
8
+ #
9
+ # This implementation is actually a NAry-Minus, since it handles
10
+ # an arbitrary number of operands.
11
+ #
12
+ class Minus
13
+ include Operator::Nary
14
+
15
+ def initialize(type, operands)
16
+ @type = type
17
+ @operands = operands
18
+ end
19
+
20
+ public
21
+
22
+ def each(&bl)
23
+ return to_enum unless block_given?
24
+ initial = operands[0].to_set
25
+ tuples = operands.drop(1).inject(initial) do |agg, op|
26
+ agg - op.to_set
27
+ end
28
+ tuples.each(&bl)
29
+ end
30
+
31
+ def to_ast
32
+ [ :minus ] + operands.map(&:to_ast)
33
+ end
34
+
35
+ protected ### optimization
36
+
37
+ def _minus(type, other)
38
+ return self if other.is_a?(Relation::Empty)
39
+ case other
40
+ when Minus
41
+ Minus.new(type, operands + other.operands)
42
+ else
43
+ Minus.new(type, operands + [other])
44
+ end
45
+ end
46
+
47
+ end # class Union
48
+ end # module Operator
49
+ end # module Bmg
@@ -76,6 +76,8 @@ module Bmg
76
76
  v.nil? ? rr[k] || k : [rr[k] || k, v]
77
77
  }
78
78
  operand.page(ordering, page_index, options).rename(renaming)
79
+ rescue UnsupportedError
80
+ super
79
81
  end
80
82
 
81
83
  def _restrict(type, predicate)
data/lib/bmg/operator.rb CHANGED
@@ -39,6 +39,7 @@ require_relative 'operator/group'
39
39
  require_relative 'operator/image'
40
40
  require_relative 'operator/join'
41
41
  require_relative 'operator/matching'
42
+ require_relative 'operator/minus'
42
43
  require_relative 'operator/not_matching'
43
44
  require_relative 'operator/page'
44
45
  require_relative 'operator/project'
@@ -157,7 +157,7 @@ module Bmg
157
157
  builder :group_by_clause
158
158
 
159
159
  def order_by_clause(ordering, &desaliaser)
160
- ordering.to_a.map{|(name,direction)|
160
+ ordering.map{|(name,direction)|
161
161
  name = name.to_s
162
162
  name = (desaliaser && desaliaser[name]) || column_name(name)
163
163
  [:order_by_term, name, direction ? direction.to_s : "asc"]
@@ -120,12 +120,15 @@ module Bmg
120
120
  end
121
121
 
122
122
  def _page(type, ordering, page_index, options)
123
+ pairs = Ordering.new(ordering).to_pairs
123
124
  limit = options[:page_size] || Operator::Page::DEFAULT_OPTIONS[:page_size]
124
125
  offset = (page_index - 1) * limit
125
126
  expr = before_use(self.expr)
126
- expr = Processor::OrderBy.new(ordering, builder).call(expr)
127
+ expr = Processor::OrderBy.new(pairs, builder).call(expr)
127
128
  expr = Processor::LimitOffset.new(limit, offset, builder).call(expr)
128
129
  _instance(type, builder, expr)
130
+ rescue Bmg::NotSupportedError
131
+ super
129
132
  end
130
133
 
131
134
  def _project(type, attrlist)
@@ -191,6 +194,16 @@ module Bmg
191
194
  end
192
195
  end
193
196
 
197
+ def _minus(type, right)
198
+ if right_expr = extract_compatible_sexpr(right)
199
+ expr = before_use(self.expr)
200
+ expr = Processor::Merge.new(:except, false, right_expr, builder).call(expr)
201
+ _instance(type, builder, expr)
202
+ else
203
+ super
204
+ end
205
+ end
206
+
194
207
  # Build a new relation instance for some new type & expression
195
208
  #
196
209
  # This method can be overriden by subclasses to provide their
data/lib/bmg/sql.rb CHANGED
@@ -3,7 +3,7 @@ module Bmg
3
3
 
4
4
  module Sql
5
5
 
6
- class NotSupportedError < Bmg::Error; end
6
+ class NotSupportedError < Bmg::NotSupportedError; end
7
7
 
8
8
  end # module Sql
9
9
 
@@ -1,47 +1,87 @@
1
1
  module Bmg
2
- class Ordering
2
+ module Ordering
3
3
 
4
- def initialize(attrs)
5
- @attrs = if attrs.empty?
6
- []
7
- elsif attrs.first.is_a?(Symbol)
8
- attrs.map{|a| [a, :asc] }
4
+ # Factory over subclasses
5
+ def self.new(arg)
6
+ case arg
7
+ when Ordering
8
+ arg
9
+ when Proc
10
+ Native.new(arg)
9
11
  else
10
- attrs
12
+ Attributes.new(arg)
11
13
  end
12
14
  end
13
- attr_reader :attrs
14
15
 
15
16
  def call(t1, t2)
16
17
  comparator.call(t1, t2)
17
18
  end
18
19
 
19
- def comparator
20
- @comparator ||= ->(t1, t2) { compare_attrs(t1, t2) }
20
+ def to_a
21
+ to_pairs
21
22
  end
22
23
 
23
- def compare_attrs(t1, t2)
24
- attrs.each do |(attr,direction)|
25
- a1, a2 = t1[attr], t2[attr]
26
- if a1.nil? && a2.nil?
27
- 0
28
- elsif a1.nil?
29
- return direction == :desc ? -1 : 1
30
- elsif a2.nil?
31
- return direction == :desc ? 1 : -1
32
- elsif a1.respond_to?(:<=>)
33
- c = a1 <=> a2
34
- unless c.nil? || c==0
35
- return direction == :desc ? -c : c
36
- end
24
+ def map(*args, &bl)
25
+ to_pairs.map(*args, &bl)
26
+ end
27
+
28
+ class Native
29
+ include Ordering
30
+
31
+ def initialize(comparator)
32
+ @comparator = comparator
33
+ end
34
+ attr_reader :comparator
35
+
36
+ def to_pairs
37
+ raise Bmg::NotSupportedError
38
+ end
39
+
40
+ end # class Native
41
+
42
+ class Attributes
43
+ include Ordering
44
+
45
+ def initialize(attrs)
46
+ @attrs = if attrs.empty?
47
+ []
48
+ elsif attrs.first.is_a?(Symbol)
49
+ attrs.map{|a| [a, :asc] }
50
+ else
51
+ attrs
37
52
  end
38
53
  end
39
- 0
40
- end
54
+ attr_reader :attrs
41
55
 
42
- def to_a
43
- attrs.to_a
44
- end
56
+ def comparator
57
+ @comparator ||= ->(t1, t2) { compare_attrs(t1, t2) }
58
+ end
59
+
60
+ def to_pairs
61
+ attrs.to_a
62
+ end
63
+
64
+ private
65
+
66
+ def compare_attrs(t1, t2)
67
+ attrs.each do |(attr,direction)|
68
+ a1, a2 = t1[attr], t2[attr]
69
+ if a1.nil? && a2.nil?
70
+ 0
71
+ elsif a1.nil?
72
+ return direction == :desc ? -1 : 1
73
+ elsif a2.nil?
74
+ return direction == :desc ? 1 : -1
75
+ elsif a1.respond_to?(:<=>)
76
+ c = a1 <=> a2
77
+ unless c.nil? || c==0
78
+ return direction == :desc ? -c : c
79
+ end
80
+ end
81
+ end
82
+ 0
83
+ end
45
84
 
46
- end # class Ordering
85
+ end # class Attributes
86
+ end # module Ordering
47
87
  end # module Bmg
data/lib/bmg/type.rb CHANGED
@@ -288,12 +288,7 @@ module Bmg
288
288
  end
289
289
 
290
290
  def union(other)
291
- if typechecked? && knows_attrlist? && other.knows_attrlist?
292
- missing = self.attrlist - other.attrlist
293
- raise TypeError, "Union incompatible: missing right attributes #{missing.join(', ')}" unless missing.empty?
294
- extra = other.attrlist - self.attrlist
295
- raise TypeError, "Union incompatible: missing left attributes #{extra.join(', ')}" unless extra.empty?
296
- end
291
+ union_compatible!(other, "Union")
297
292
  dup.tap{|x|
298
293
  ### attrlist stays the same
299
294
  x.predicate = self.predicate | predicate
@@ -301,6 +296,17 @@ module Bmg
301
296
  }
302
297
  end
303
298
 
299
+ def minus(other)
300
+ union_compatible!(other, "Minus")
301
+ dup.tap{|x|
302
+ # 1. attributes do not change
303
+ # 2. predicate does not change, we can't strengthen it in any way but it
304
+ # does not become weaker either
305
+ # 3. we can safely keep all existing keys, but it's not obvious we can
306
+ # gain some new ones
307
+ }
308
+ end
309
+
304
310
  def unwrap(attrlist)
305
311
  known_attributes!(attrlist) if typechecked? && knows_attrlist?
306
312
  dup.tap{|x|
@@ -338,5 +344,14 @@ module Bmg
338
344
  end
339
345
  end
340
346
 
347
+ def union_compatible!(other, opname)
348
+ if typechecked? && knows_attrlist? && other.knows_attrlist?
349
+ missing = self.attrlist - other.attrlist
350
+ raise TypeError, "#{opname} requires compatible attribute lists, but the right operand is missing the following attributes: #{missing.join(', ')}" unless missing.empty?
351
+ extra = other.attrlist - self.attrlist
352
+ raise TypeError, "#{opname} requires compatible attribute lists, but the left operand is missing the following attributes: #{extra.join(', ')}" unless extra.empty?
353
+ end
354
+ end
355
+
341
356
  end # class Type
342
357
  end # module Bmg
data/lib/bmg/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 21
5
- TINY = 5
4
+ MINOR = 22
5
+ TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
data/lib/bmg/writer.rb CHANGED
@@ -16,7 +16,7 @@ module Bmg
16
16
  if ordering = output_preferences.tuple_ordering
17
17
  relation
18
18
  .to_a
19
- .sort{|t1,t2| ordering.compare_attrs(t1, t2) }
19
+ .sort{|t1,t2| ordering.call(t1, t2) }
20
20
  .each_with_index(&bl)
21
21
  else
22
22
  relation.each_with_index(&bl)
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.21.5
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-24 00:00:00.000000000 Z
11
+ date: 2024-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: predicate
@@ -153,6 +153,7 @@ files:
153
153
  - lib/bmg/operator/image.rb
154
154
  - lib/bmg/operator/join.rb
155
155
  - lib/bmg/operator/matching.rb
156
+ - lib/bmg/operator/minus.rb
156
157
  - lib/bmg/operator/not_matching.rb
157
158
  - lib/bmg/operator/page.rb
158
159
  - lib/bmg/operator/project.rb