bmg 0.3.0 → 0.4.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: e6536b48ec764ca63860c01d8f75b689449c0d29
4
- data.tar.gz: 4a5f92f4e5dc65f723e419ccbfe89dd424aa27ef
3
+ metadata.gz: 5cdd3816b79a6f29c387456d9ffdc12d9ae7872e
4
+ data.tar.gz: 4c30157082a6474c76a156efc7fcdb510f2f2d44
5
5
  SHA512:
6
- metadata.gz: 3d9fff6fdabf0f3be88b94cff5a7896d0625d82c47772683c3f40156c7cefa46387c95131c261aeb953978aec013e16acae40d203a9554233a61604fea59e5bb
7
- data.tar.gz: 6b8b239b5da4002e2ee96a0946acf7b9636f465f15eeac0f5ee227dc4fcccab68fa00b4b4a32a4827b0e9336e5f46a73036397dabe9855e6ea14351a56279fe8
6
+ metadata.gz: 95290af1dba5768f4a4bfee0357d53ddd70063a6fb6a34ee8c6b329805f44ec1ac49ba0fd5baea0b6946d4e81bf3250b30a954f2b1288c53e0eaa4019687e6d8
7
+ data.tar.gz: 95fb7f28fbaf62b2f12286857bd04ccc0849d397f085bd366f303572e21a01d6baeb023d4226f56b8223182f075f0c455a3ef06aa7433c677fa5390404c4b9d9
@@ -82,12 +82,12 @@ module Bmg
82
82
  end
83
83
  protected :_restrict
84
84
 
85
- def union(other)
86
- _union self.type.union(other.type), other
85
+ def union(other, options = {})
86
+ _union self.type.union(other.type), other, options
87
87
  end
88
88
 
89
- def _union(type, other)
90
- Operator::Union.new(type, self, other)
89
+ def _union(type, other, options)
90
+ Operator::Union.new(type, [self, other], options)
91
91
  end
92
92
  protected :_union
93
93
 
@@ -14,5 +14,9 @@ module Bmg
14
14
  @operand.each(&bl)
15
15
  end
16
16
 
17
+ def to_ast
18
+ [:leaf, operand]
19
+ end
20
+
17
21
  end
18
22
  end
@@ -41,6 +41,10 @@ module Bmg
41
41
  end
42
42
  end
43
43
 
44
+ def to_ast
45
+ [:allbut, operand.to_ast, butlist.dup]
46
+ end
47
+
44
48
  protected ### optimization
45
49
 
46
50
  def _restrict(type, predicate)
@@ -38,6 +38,10 @@ module Bmg
38
38
  h.values.each(&bl)
39
39
  end
40
40
 
41
+ def to_ast
42
+ [:autosummarize, operand.to_ast, by.dup, sums.dup]
43
+ end
44
+
41
45
  private
42
46
 
43
47
  # Returns the tuple determinant.
@@ -28,6 +28,7 @@ module Bmg
28
28
  def initialize(type, operand, options = {})
29
29
  @type = type
30
30
  @operand = operand
31
+ @original_options = options
31
32
  @options = DEFAULT_OPTIONS.merge(options)
32
33
  @options[:postprocessor] = NoLeftJoinNoise.new(@options[:postprocessor])
33
34
  end
@@ -45,6 +46,10 @@ module Bmg
45
46
  end
46
47
  end
47
48
 
49
+ def to_ast
50
+ [ :autowrap, operand.to_ast, @original_options.dup ]
51
+ end
52
+
48
53
  private
49
54
 
50
55
  def autowrap(tuple)
@@ -29,17 +29,41 @@ module Bmg
29
29
  end
30
30
  end
31
31
 
32
+ def to_ast
33
+ [ :constants, operand.to_ast, constants.dup ]
34
+ end
35
+
32
36
  protected ### optimization
33
37
 
34
38
  def _restrict(type, predicate)
35
39
  top_p, bottom_p = predicate.and_split(constants.keys)
36
- if top_p == predicate
40
+ if top_p.tautology?
41
+ # push all situation
42
+ result = operand
43
+ result = result.restrict(bottom_p) unless bottom_p.tautology?
44
+ result = result.constants(constants)
45
+ result
46
+ elsif (top_p.free_variables - constants.keys).empty?
47
+ # top_p applies to constants only
48
+ if eval = top_p.evaluate(constants)
49
+ result = operand
50
+ result = result.restrict(bottom_p) unless bottom_p.tautology?
51
+ result = result.constants(constants)
52
+ result
53
+ else
54
+ Relation.empty(type)
55
+ end
56
+ elsif bottom_p.tautology?
57
+ # push none situation, no optimization possible since top_p
58
+ # is not a tautology
37
59
  super
38
60
  else
61
+ # top_p and bottom_p are complex predicates. Let apply each
62
+ # of them
39
63
  result = operand
40
- result = result.restrict(bottom_p) unless bottom_p.tautology?
64
+ result = result.restrict(bottom_p)
41
65
  result = result.constants(constants)
42
- result = result.restrict(top_p) unless top_p.tautology?
66
+ result = result.restrict(top_p)
43
67
  result
44
68
  end
45
69
  rescue Predicate::NotSupportedError
@@ -32,6 +32,10 @@ module Bmg
32
32
  end
33
33
  end
34
34
 
35
+ def to_ast
36
+ [ :extend, operand.to_ast, extension.dup ]
37
+ end
38
+
35
39
  private
36
40
 
37
41
  def extend_it(tuple)
@@ -50,6 +50,28 @@ module Bmg
50
50
  end
51
51
  end
52
52
 
53
+ def to_ast
54
+ [ :image, left.to_ast, right.to_ast, as, on, options.dup ]
55
+ end
56
+
57
+ protected ### optimization
58
+
59
+ def _restrict(type, predicate)
60
+ on_as, rest = predicate.and_split([as])
61
+ if on_as == predicate
62
+ super
63
+ else
64
+ shared, left_only = rest.and_split(on)
65
+ new_left = left.restrict(rest)
66
+ new_right = shared.tautology? ? right : right.restrict(shared)
67
+ opt = new_left.image(new_right, as, on, options)
68
+ opt = opt.restrict(on_as) unless on_as.tautology?
69
+ opt
70
+ end
71
+ rescue Predicate::NotSupportedError
72
+ super
73
+ end
74
+
53
75
  private
54
76
 
55
77
  def tuple_project(tuple, on)
@@ -40,6 +40,10 @@ module Bmg
40
40
  end
41
41
  end
42
42
 
43
+ def to_ast
44
+ [ :project, operand.to_ast, attrlist ]
45
+ end
46
+
43
47
  protected ### optimization
44
48
 
45
49
  def _restrict(type, predicate)
@@ -35,6 +35,10 @@ module Bmg
35
35
  end
36
36
  end
37
37
 
38
+ def to_ast
39
+ [ :rename, operand.to_ast, renaming.dup ]
40
+ end
41
+
38
42
  private
39
43
 
40
44
  def rename(tuple)
@@ -28,6 +28,10 @@ module Bmg
28
28
  end
29
29
  end
30
30
 
31
+ def to_ast
32
+ [ :restrict, operand.to_ast, predicate.sexpr ]
33
+ end
34
+
31
35
  protected
32
36
 
33
37
  def _restrict(type, predicate)
@@ -6,6 +6,9 @@ module Bmg
6
6
  # Returns all tuples of the left operand followed by all
7
7
  # tuples from the right operand.
8
8
  #
9
+ # This implementation is actually a NAry-Union, since it handles
10
+ # an arbitrary number of operands.
11
+ #
9
12
  # By default, this operator strips duplicates, as of relational
10
13
  # theory. Please set the `:all` option to true to avoid this
11
14
  # behavior and save execution time.
@@ -17,17 +20,16 @@ module Bmg
17
20
  all: false
18
21
  }
19
22
 
20
- def initialize(type, left, right, options = {})
23
+ def initialize(type, operands, options = {})
21
24
  @type = type
22
- @left = left
23
- @right = right
25
+ @operands = operands
24
26
  @options = DEFAULT_OPTIONS.merge(options)
25
27
  end
26
28
  attr_reader :type
27
29
 
28
30
  protected
29
31
 
30
- attr_reader :left, :right, :options
32
+ attr_reader :operands, :options
31
33
 
32
34
  public
33
35
 
@@ -37,24 +39,48 @@ module Bmg
37
39
 
38
40
  def each(&bl)
39
41
  if all?
40
- @left.each(&bl)
41
- @right.each(&bl)
42
+ operands.each do |op|
43
+ op.each(&bl)
44
+ end
42
45
  else
43
46
  seen = {}
44
- @left.each do |tuple|
45
- yield(tuple)
46
- seen[tuple] = true
47
- end
48
- @right.each do |tuple|
49
- yield(tuple) unless seen.has_key?(tuple)
47
+ operands.each do |op|
48
+ op.each do |tuple|
49
+ yield(tuple) unless seen.has_key?(tuple)
50
+ seen[tuple] = true
51
+ end
50
52
  end
51
53
  end
52
54
  end
53
55
 
56
+ def to_ast
57
+ [ :union ] + operands.map(&:to_ast) + [ options.dup ]
58
+ end
59
+
54
60
  protected ### optimization
55
61
 
56
62
  def _restrict(type, predicate)
57
- left.restrict(predicate).union(right.restrict(predicate))
63
+ ops = operands
64
+ .map {|op| op.restrict(predicate) }
65
+ .reject{|op| op.is_a?(Relation::Empty) }
66
+ case ops.size
67
+ when 0 then Relation.empty(type)
68
+ when 1 then ops.first
69
+ else Union.new(type, ops, options)
70
+ end
71
+ end
72
+
73
+ def _union(type, other, options)
74
+ return self if other.is_a?(Relation::Empty)
75
+ norm_options = DEFAULT_OPTIONS.merge(options)
76
+ return super unless norm_options == self.options
77
+ case other
78
+ when Union
79
+ return super unless norm_options == other.send(:options)
80
+ Union.new(type, operands + other.operands, options)
81
+ else
82
+ Union.new(type, operands + [other], options)
83
+ end
58
84
  end
59
85
 
60
86
  end # class Union
@@ -7,6 +7,10 @@ module Bmg
7
7
  operand.is_a?(Relation) ? operand : Leaf.new(type, operand)
8
8
  end
9
9
 
10
+ def self.empty(type = Type::ANY)
11
+ Relation::Empty.new(type)
12
+ end
13
+
10
14
  # Private helper to implement `one` and `one_or_nil`
11
15
  def one_or_yield(&bl)
12
16
  first = nil
@@ -31,5 +35,31 @@ module Bmg
31
35
  one_or_yield{ nil }
32
36
  end
33
37
 
38
+ # Converts to an sexpr expression.
39
+ def to_ast
40
+ raise "Bmg is missing a feature!"
41
+ end
42
+
43
+ # Returns a String representing the query plan
44
+ def debug(max_level = nil, on = STDERR)
45
+ on.puts _debug(to_ast, 1, max_level)
46
+ self
47
+ end
48
+
49
+ private
50
+
51
+ def _debug(ast, level = 1, max_level = nil)
52
+ return ast.inspect if ast.is_a?(Symbol)
53
+ return ast.to_s unless ast.is_a?(Array)
54
+ return ast.to_s unless ast.first.is_a?(Symbol)
55
+ if max_level && level>max_level
56
+ "(#{ast.first} ...)"
57
+ else
58
+ sep = " " * level
59
+ "(#{ast.first}\n" + ast[1..-1].map{|a| _debug(a, 1+level, max_level) }.join("\n").gsub(/^/, sep) + ")"
60
+ end
61
+ end
62
+
34
63
  end # module Relation
35
64
  end # module Bmg
65
+ require_relative 'relation/empty'
@@ -0,0 +1,65 @@
1
+ module Bmg
2
+ module Relation
3
+ #
4
+ # The empty relation, of a given type.
5
+ #
6
+ # This relation implementation exists mostly for optimization
7
+ # purposes, since knowing that a relation is empty allows
8
+ # simplifying many expressions.
9
+ #
10
+ class Empty
11
+ include Relation
12
+
13
+ def initialize(type)
14
+ @type = type
15
+ end
16
+ attr_reader :type
17
+
18
+ def each(&bl)
19
+ end
20
+
21
+ def to_ast
22
+ [ :empty ]
23
+ end
24
+
25
+ protected ### optimization
26
+
27
+ def _allbut(type, *args)
28
+ Empty.new(type)
29
+ end
30
+
31
+ def _autosummarize(type, *args)
32
+ Empty.new(type)
33
+ end
34
+
35
+ def _autowrap(type, *args)
36
+ Empty.new(type)
37
+ end
38
+
39
+ def _extend(type, *args)
40
+ Empty.new(type)
41
+ end
42
+
43
+ def _image(type, *args)
44
+ Empty.new(type)
45
+ end
46
+
47
+ def _project(type, *args)
48
+ Empty.new(type)
49
+ end
50
+
51
+ def _rename(type, *args)
52
+ Empty.new(type)
53
+ end
54
+
55
+ def _restrict(type, predicate)
56
+ self
57
+ end
58
+
59
+ def _union(type, other, options)
60
+ other
61
+ end
62
+
63
+ end # class Empty
64
+ end # module Relation
65
+ end # module Bmg
@@ -19,6 +19,10 @@ module Bmg
19
19
  @dataset.each(&bl)
20
20
  end
21
21
 
22
+ def to_ast
23
+ [:sequel, @dataset.sql]
24
+ end
25
+
22
26
  protected ### optimization
23
27
 
24
28
  def _restrict(type, predicate)
@@ -1,7 +1,7 @@
1
1
  module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 3
4
+ MINOR = 4
5
5
  TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bmg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
@@ -144,6 +144,7 @@ files:
144
144
  - lib/bmg/reader/csv.rb
145
145
  - lib/bmg/reader/excel.rb
146
146
  - lib/bmg/relation.rb
147
+ - lib/bmg/relation/empty.rb
147
148
  - lib/bmg/sequel.rb
148
149
  - lib/bmg/sequel/relation.rb
149
150
  - lib/bmg/type.rb