bmg 0.21.5 → 0.22.0

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