bmg 0.21.4 → 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: 59da4b61f1d6c332de148552eab6277032df750c
4
- data.tar.gz: b65877260b6a5eca59fb864f52a08dad26f917f4
3
+ metadata.gz: be420623e04afe03570a1756d1607a202eb96481
4
+ data.tar.gz: 150b4f3f4daced5825e661765ed4530455263422
5
5
  SHA512:
6
- metadata.gz: fd85e6e179eb6e1a4d9ffe2f074db3c0d856d239bc65b8d535d6d14cff987a0af1eed75046aed0538726ff87925b9a2a400153a1acec7d7ecc3cc7fdc50d0e87
7
- data.tar.gz: 5954aad879e293d34eb175ff54055473e3a983e6a16ec816694eb0b16fd90897cbf9d31bcdf53bfdef664891abb42cb538c7962954d76331c291411e816c956b
6
+ metadata.gz: 5b2d86803d1bfedfa5605f158a34c744e8b0fa77ed25314e38a28dac6bdef1512bc51a3398cfcd55df42fb60354880b4378b49d339682724cd3da42ef282ef27
7
+ data.tar.gz: 7321a2b290d2852ff4e2fe52b915e190d3fb13143807e8ac062928c8745a330d70c43fcbbf4abb545507cc8d2b9a05167502c44c7b6d5310c24b2e781920f827
data/README.md CHANGED
@@ -197,15 +197,17 @@ type = Bmg::Type::ANY.with_keys([[:id]])
197
197
  r = Bmg.redis(type, {
198
198
  key_prefix: "suppliers",
199
199
  redis: Redis.new,
200
- serializer: :marshal
200
+ serializer: :marshal,
201
+ ttl: 365 * 24 * 60 * 60
201
202
  })
202
203
  ```
203
204
 
204
- The key prefix will be used to distinguish the tuples from other
205
- elements in the same database (e.g. tuples from other relvars).
206
- The serializer is either `:marshal` or `:json`. Please note that
207
- types are not preserved when using the second one (all attribute
208
- values will come back as strings, but keys will be symbolized).
205
+ The key prefix will be used to distinguish the tuples from other elements in the
206
+ same database (e.g. tuples from other relvars). The serializer is either
207
+ `:marshal` or `:json`. Please note that types are not preserved when using the
208
+ second one (all attribute values will come back as strings, but keys will be
209
+ symbolized). The `ttl` is used to set the validity period of a tuple in redis
210
+ and is optional.
209
211
 
210
212
  The redis relvars support basic algorithms for insert/update/delete.
211
213
  No optimization is currently supported.
@@ -252,12 +254,13 @@ r.exclude(predicate) # shortcut for restrict(!predicate)
252
254
  r.group([:a, :b, ...], :x) # relation-valued attribute from attributes
253
255
  r.image(right, :x, [:a, :b, ...]) # relation-valued attribute from another relation
254
256
  r.images({:x => r1, :y => r2}, [:a, ...]) # shortcut over image(r1, :x, ...).image(r2, :y, ...)
255
- r.join(right, [:a, :b, ...]) # natural join on a join key
256
- r.join(right, :a => :x, :b => :y, ...) # natural join after right reversed renaming
257
+ r.join(right, [:a, :b, ...]) # join on a join key
258
+ r.join(right, :a => :x, :b => :y, ...) # join after right reversed renaming
257
259
  r.left_join(right, [:a, :b, ...], {...}) # left join with optional default right tuple
258
260
  r.left_join(right, {:a => :x, ...}, {...}) # left join after right reversed renaming
259
261
  r.matching(right, [:a, :b, ...]) # semi join, aka where exists
260
262
  r.matching(right, :a => :x, :b => :y, ...) # semi join, after right reversed renaming
263
+ r.minus(right) # set difference
261
264
  r.not_matching(right, [:a, :b, ...]) # inverse semi join, aka where not exists
262
265
  r.not_matching(right, :a => :x, ...) # inverse semi join, after right reversed renaming
263
266
  r.page([[:a, :asc], ...], 12, page_size: 10) # paging, using an explicit ordering
@@ -268,13 +271,13 @@ r.restrict(a: "foo", b: "bar", ...) # relational restriction, aka where
268
271
  r.rxmatch([:a, :b, ...], /xxx/) # regex match kind of restriction
269
272
  r.summarize([:a, :b, ...], x: :sum) # relational summarization
270
273
  r.suffix(:_foo, but: [:a, ...]) # suffix kind of renaming
271
- t.transform(:to_s) # all-attrs transformation
272
- t.transform(&:to_s) # similar, but Proc-driven
273
- t.transform(:foo => :upcase, ...) # specific-attrs tranformation
274
- t.transform([:to_s, :upcase]) # chain-transformation
274
+ r.transform(:to_s) # all-attrs transformation
275
+ r.transform(&:to_s) # similar, but Proc-driven
276
+ r.transform(:foo => :upcase, ...) # specific-attrs tranformation
277
+ r.transform([:to_s, :upcase]) # chain-transformation
275
278
  r.ungroup([:a, :b, ...]) # ungroup relation-valued attributes within parent tuple
276
279
  r.ungroup(:a) # shortcut over ungroup([:a])
277
- r.union(right) # relational union
280
+ r.union(right) # set union
278
281
  r.unwrap([:a, :b, ...]) # merge tuple-valued attributes within parent tuple
279
282
  r.unwrap(:a) # shortcut over unwrap([:a])
280
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 = 4
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.4
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-03-08 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