bmg 0.16.6 → 0.17.3
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 +4 -4
- data/Gemfile +3 -0
- data/lib/bmg/algebra.rb +13 -0
- data/lib/bmg/operator/image.rb +120 -9
- data/lib/bmg/operator/join.rb +24 -4
- data/lib/bmg/operator/restrict.rb +4 -0
- data/lib/bmg/operator/shared/binary.rb +12 -1
- data/lib/bmg/operator/shared/nary.rb +9 -1
- data/lib/bmg/operator/shared/unary.rb +9 -1
- data/lib/bmg/relation.rb +4 -0
- data/lib/bmg/sequel/translator.rb +14 -2
- data/lib/bmg/sql/grammar.rb +3 -0
- data/lib/bmg/sql/grammar.sexp.yml +10 -0
- data/lib/bmg/sql/nodes/cross_join.rb +1 -12
- data/lib/bmg/sql/nodes/func_call.rb +23 -0
- data/lib/bmg/sql/nodes/inner_join.rb +3 -25
- data/lib/bmg/sql/nodes/join.rb +44 -0
- data/lib/bmg/sql/nodes/left_join.rb +15 -0
- data/lib/bmg/sql/processor.rb +1 -0
- data/lib/bmg/sql/processor/bind.rb +23 -0
- data/lib/bmg/sql/processor/join.rb +33 -5
- data/lib/bmg/sql/processor/requalify.rb +1 -1
- data/lib/bmg/sql/processor/where.rb +2 -2
- data/lib/bmg/sql/relation.rb +21 -1
- data/lib/bmg/sql/support/from_clause_orderer.rb +41 -23
- data/lib/bmg/type.rb +9 -0
- data/lib/bmg/version.rb +2 -2
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90ed0f35cc6ced83ada1d6aeb3162b7fb8c1bb3f6f20f870f030c589e991e97e
|
4
|
+
data.tar.gz: 959415999253759d072fae5e5280417cdbd5fd8774bbf90e541b74f2324d197c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fbb3cb7ea042873933e6c15f5ee73084980066cc3ee8c8c58c5169b3221a1aa4c2d97a1521618c456da0d34097df456ef34081a8a418429d1627ed2ada36c1ec
|
7
|
+
data.tar.gz: 687eed5fc05caa9c7dba8258aeb70b929877d6c56c45212ac04358df562acf4ffdc57f199c8d774d43631f82088fa70cac78b1754ae2bfbdd8eaa4d61b156014
|
data/Gemfile
CHANGED
data/lib/bmg/algebra.rb
CHANGED
@@ -80,6 +80,19 @@ module Bmg
|
|
80
80
|
end
|
81
81
|
protected :_joined_with
|
82
82
|
|
83
|
+
def left_join(right, on = [], default_right_tuple = {})
|
84
|
+
drt = default_right_tuple
|
85
|
+
_left_join self.type.left_join(right.type, on, drt), right, on, drt
|
86
|
+
end
|
87
|
+
|
88
|
+
def _left_join(type, right, on, default_right_tuple)
|
89
|
+
Operator::Join.new(type, self, right, on, {
|
90
|
+
variant: :left,
|
91
|
+
default_right_tuple: default_right_tuple
|
92
|
+
})
|
93
|
+
end
|
94
|
+
protected :_left_join
|
95
|
+
|
83
96
|
def matching(right, on = [])
|
84
97
|
_matching self.type.matching(right.type, on), right, on
|
85
98
|
end
|
data/lib/bmg/operator/image.rb
CHANGED
@@ -12,7 +12,21 @@ module Bmg
|
|
12
12
|
|
13
13
|
# Whether we need to convert each image as an Array,
|
14
14
|
# instead of keeping a Relation instance
|
15
|
-
array: false
|
15
|
+
array: false,
|
16
|
+
|
17
|
+
# The strategy to use for actual image algorithm. Default is
|
18
|
+
# :refilter_right. Possible values are:
|
19
|
+
#
|
20
|
+
# - :index_right : builds a memory index with tuples from right, then
|
21
|
+
# passes left tuples and joins them with the index values.
|
22
|
+
#
|
23
|
+
# - :refilter_right : the left operand is materialized and all
|
24
|
+
# distinct values collected. The right operand is lately restricted
|
25
|
+
# to only those matching values. :index_right is then applied on
|
26
|
+
# resulting operabds. This option only applies when (optimized) `on`
|
27
|
+
# contains one attribute only. ; it fallbacks on :index_right
|
28
|
+
# otherwise.
|
29
|
+
strategy: :refilter_right
|
16
30
|
|
17
31
|
}
|
18
32
|
|
@@ -31,7 +45,55 @@ module Bmg
|
|
31
45
|
|
32
46
|
public
|
33
47
|
|
34
|
-
def each
|
48
|
+
def each(*args, &bl)
|
49
|
+
(options[:jit_optimized] ? self : jit_optimize)._each(*args, &bl)
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_ast
|
53
|
+
[ :image, left.to_ast, right.to_ast, as, on, options.dup ]
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def _each(*args, &bl)
|
59
|
+
case s = options[:strategy]
|
60
|
+
when :index_right then _each_index_right(*args, &bl)
|
61
|
+
when :refilter_right then _each_refilter_right(*args, &bl)
|
62
|
+
else
|
63
|
+
raise ArgumentError, "Unknown strategy `#{s}`"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def _each_index_right(*args, &bl)
|
68
|
+
left_rel, right_rel = self.left, self.right
|
69
|
+
_each_implem(left_rel, right_rel, *args, &bl)
|
70
|
+
end
|
71
|
+
|
72
|
+
def _each_refilter_right(*args, &bl)
|
73
|
+
left_rel, right_rel = self.left, self.right
|
74
|
+
|
75
|
+
# find matching keys on left and rebind the right
|
76
|
+
# placeholder to them
|
77
|
+
values = left_rel.map{|t| t[on.first] }
|
78
|
+
placeholder = options[:refilter_right][:placeholder]
|
79
|
+
right_rel = right_rel.bind(placeholder => values)
|
80
|
+
|
81
|
+
_each_implem(left_rel, right_rel, *args, &bl)
|
82
|
+
end
|
83
|
+
|
84
|
+
def _each_implem(left_rel, right_rel, *args)
|
85
|
+
# build right index
|
86
|
+
index = build_right_index(right_rel)
|
87
|
+
|
88
|
+
# each left with image from right index
|
89
|
+
left_rel.each do |tuple|
|
90
|
+
key = tuple_project(tuple, on)
|
91
|
+
image = index[key] || (options[:array] ? [] : empty_image)
|
92
|
+
yield tuple.merge(as => image)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_right_index(right)
|
35
97
|
index = Hash.new{|h,k| h[k] = empty_image }
|
36
98
|
right.each_with_object(index) do |t, index|
|
37
99
|
key = tuple_project(t, on)
|
@@ -42,15 +104,54 @@ module Bmg
|
|
42
104
|
ix[k] = v.to_a
|
43
105
|
end
|
44
106
|
end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
107
|
+
index
|
108
|
+
end
|
109
|
+
|
110
|
+
protected ### jit_optimization
|
111
|
+
|
112
|
+
def jit_optimize
|
113
|
+
case s = options[:strategy]
|
114
|
+
when :index_right then jit_index_right
|
115
|
+
when :refilter_right then jit_refilter_right
|
116
|
+
else
|
117
|
+
raise ArgumentError, "Unknown strategy `#{s}`"
|
49
118
|
end
|
50
119
|
end
|
51
120
|
|
52
|
-
def
|
53
|
-
|
121
|
+
def jit_index_right
|
122
|
+
Image.new(
|
123
|
+
type,
|
124
|
+
left,
|
125
|
+
right,
|
126
|
+
as,
|
127
|
+
on,
|
128
|
+
options.merge(jit_optimized: true))
|
129
|
+
end
|
130
|
+
|
131
|
+
def jit_refilter_right
|
132
|
+
ltc = left.type.predicate.constants
|
133
|
+
rtc = right.type.predicate.constants
|
134
|
+
jit_allbut, jit_on = on.partition{|attr|
|
135
|
+
ltc.has_key?(attr) && rtc.has_key?(attr) && ltc[attr] == rtc[attr]
|
136
|
+
}
|
137
|
+
if jit_on.size == 1
|
138
|
+
p = Predicate.placeholder
|
139
|
+
Image.new(
|
140
|
+
type,
|
141
|
+
left.materialize,
|
142
|
+
right.restrict(Predicate.in(jit_on.first, p)).allbut(jit_allbut),
|
143
|
+
as,
|
144
|
+
jit_on,
|
145
|
+
options.merge(jit_optimized: true, refilter_right: { placeholder: p }))
|
146
|
+
else
|
147
|
+
Image.new(
|
148
|
+
type,
|
149
|
+
left,
|
150
|
+
right.allbut(jit_allbut),
|
151
|
+
as,
|
152
|
+
jit_on,
|
153
|
+
options.merge(jit_optimized: true, strategy: :index_right))
|
154
|
+
end
|
54
155
|
end
|
55
156
|
|
56
157
|
protected ### optimization
|
@@ -68,7 +169,7 @@ module Bmg
|
|
68
169
|
def _restrict(type, predicate)
|
69
170
|
on_as, rest = predicate.and_split([as])
|
70
171
|
if rest.tautology?
|
71
|
-
# push
|
172
|
+
# push index_right situation: on_as is still the full predicate
|
72
173
|
super
|
73
174
|
else
|
74
175
|
# rest makes no reference to `as` and can be pushed
|
@@ -126,6 +227,16 @@ module Bmg
|
|
126
227
|
Relation::InMemory.new(image_type, Set.new)
|
127
228
|
end
|
128
229
|
|
230
|
+
public
|
231
|
+
|
232
|
+
def to_s
|
233
|
+
options[:jit_optimized] ? super : jit_optimize.to_s
|
234
|
+
end
|
235
|
+
|
236
|
+
def inspect
|
237
|
+
options[:jit_optimized] ? super : jit_optimize.inspect
|
238
|
+
end
|
239
|
+
|
129
240
|
end # class Project
|
130
241
|
end # module Operator
|
131
242
|
end # module Bmg
|
data/lib/bmg/operator/join.rb
CHANGED
@@ -8,16 +8,19 @@ module Bmg
|
|
8
8
|
class Join
|
9
9
|
include Operator::Binary
|
10
10
|
|
11
|
-
|
11
|
+
DEFAULT_OPTIONS = {}
|
12
|
+
|
13
|
+
def initialize(type, left, right, on, options = {})
|
12
14
|
@type = type
|
13
15
|
@left = left
|
14
16
|
@right = right
|
15
17
|
@on = on
|
18
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
16
19
|
end
|
17
20
|
|
18
21
|
private
|
19
22
|
|
20
|
-
attr_reader :on
|
23
|
+
attr_reader :on, :options
|
21
24
|
|
22
25
|
public
|
23
26
|
|
@@ -34,12 +37,24 @@ module Bmg
|
|
34
37
|
to_join.each do |right|
|
35
38
|
yield right.merge(tuple)
|
36
39
|
end
|
40
|
+
elsif left_join?
|
41
|
+
yield(tuple.merge(default_right_tuple))
|
37
42
|
end
|
38
43
|
end
|
39
44
|
end
|
40
45
|
|
41
46
|
def to_ast
|
42
|
-
[ :join, left.to_ast, right.to_ast, on ]
|
47
|
+
[ :join, left.to_ast, right.to_ast, on, extra_opts ].compact
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def left_join?
|
53
|
+
options[:variant] == :left
|
54
|
+
end
|
55
|
+
|
56
|
+
def default_right_tuple
|
57
|
+
options[:default_right_tuple]
|
43
58
|
end
|
44
59
|
|
45
60
|
protected ### optimization
|
@@ -63,8 +78,13 @@ module Bmg
|
|
63
78
|
|
64
79
|
protected ### inspect
|
65
80
|
|
81
|
+
def extra_opts
|
82
|
+
extra = options.dup.delete_if{|k,v| DEFAULT_OPTIONS[k] == v }
|
83
|
+
extra.empty? ? nil : extra
|
84
|
+
end
|
85
|
+
|
66
86
|
def args
|
67
|
-
[ on ]
|
87
|
+
[ on, extra_opts ].compact
|
68
88
|
end
|
69
89
|
|
70
90
|
private
|
@@ -3,9 +3,13 @@ module Bmg
|
|
3
3
|
module Binary
|
4
4
|
include Operator
|
5
5
|
|
6
|
+
def bind(binding)
|
7
|
+
_with_operands(left.bind(binding), right.bind(binding))
|
8
|
+
end
|
9
|
+
|
6
10
|
protected
|
7
11
|
|
8
|
-
|
12
|
+
attr_accessor :left, :right
|
9
13
|
|
10
14
|
def _visit(parent, visitor)
|
11
15
|
visitor.call(self, parent)
|
@@ -13,6 +17,13 @@ module Bmg
|
|
13
17
|
right.send(:_visit, self, visitor)
|
14
18
|
end
|
15
19
|
|
20
|
+
def _with_operands(left, right)
|
21
|
+
dup.tap{|d|
|
22
|
+
d.left = left
|
23
|
+
d.right = right
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
16
27
|
def operands
|
17
28
|
[left, right]
|
18
29
|
end
|
@@ -3,9 +3,17 @@ module Bmg
|
|
3
3
|
module Nary
|
4
4
|
include Operator
|
5
5
|
|
6
|
+
def bind(binding)
|
7
|
+
_with_operands(operands.map{|op| op.bind(binding) })
|
8
|
+
end
|
9
|
+
|
6
10
|
protected
|
7
11
|
|
8
|
-
|
12
|
+
attr_accessor :operands
|
13
|
+
|
14
|
+
def _with_operands(operands)
|
15
|
+
dup.tap{|d| d.operands = operands }
|
16
|
+
end
|
9
17
|
|
10
18
|
def _visit(parent, visitor)
|
11
19
|
visitor.call(self, parent)
|
@@ -3,15 +3,23 @@ module Bmg
|
|
3
3
|
module Unary
|
4
4
|
include Operator
|
5
5
|
|
6
|
+
def bind(binding)
|
7
|
+
_with_operand(operand.bind(binding))
|
8
|
+
end
|
9
|
+
|
6
10
|
protected
|
7
11
|
|
8
|
-
|
12
|
+
attr_accessor :operand
|
9
13
|
|
10
14
|
def _visit(parent, visitor)
|
11
15
|
visitor.call(self, parent)
|
12
16
|
operand._visit(self, visitor)
|
13
17
|
end
|
14
18
|
|
19
|
+
def _with_operand(operand)
|
20
|
+
dup.tap{|d| d.operand = operand }
|
21
|
+
end
|
22
|
+
|
15
23
|
def operands
|
16
24
|
[operand]
|
17
25
|
end
|
data/lib/bmg/relation.rb
CHANGED
@@ -50,7 +50,7 @@ module Bmg
|
|
50
50
|
dataset = dataset.select(*selection)
|
51
51
|
dataset = dataset.distinct if sexpr.distinct?
|
52
52
|
dataset = dataset.where(predicate) if predicate
|
53
|
-
dataset = dataset.group(grouping)
|
53
|
+
dataset = dataset.group(*grouping) if grouping
|
54
54
|
dataset = dataset.order_by(*order) if order
|
55
55
|
dataset = dataset.limit(limit, offset == 0 ? nil : offset) if limit or offset
|
56
56
|
dataset
|
@@ -70,13 +70,18 @@ module Bmg
|
|
70
70
|
case kind = sexpr.left.first
|
71
71
|
when :qualified_name
|
72
72
|
left.column == right.value ? left : ::Sequel.as(left, right)
|
73
|
-
when :literal, :summarizer
|
73
|
+
when :literal, :summarizer, :func_call
|
74
74
|
::Sequel.as(left, right)
|
75
75
|
else
|
76
76
|
raise NotImplementedError, "Unexpected select item `#{kind}`"
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
+
def on_func_call(sexpr)
|
81
|
+
args = sexpr.func_args.map{|fa| apply(fa) }
|
82
|
+
::Sequel.function(sexpr.func_name, *args)
|
83
|
+
end
|
84
|
+
|
80
85
|
def on_summarizer(sexpr)
|
81
86
|
if sexpr.summary_expr
|
82
87
|
::Sequel.function(sexpr.summary_func, apply(sexpr.summary_expr))
|
@@ -106,6 +111,13 @@ module Bmg
|
|
106
111
|
ds.join_table(:inner, apply(table), nil, options){|*args|
|
107
112
|
apply(on)
|
108
113
|
}
|
114
|
+
elsif kind == :left_join
|
115
|
+
options = { qualify: false, table_alias: false }
|
116
|
+
ds.join_table(:left, apply(table), nil, options){|*args|
|
117
|
+
apply(on)
|
118
|
+
}
|
119
|
+
else
|
120
|
+
raise IllegalArgumentError, "Unrecognized from clause: `#{sexpr}`"
|
109
121
|
end
|
110
122
|
end
|
111
123
|
end
|
data/lib/bmg/sql/grammar.rb
CHANGED
@@ -42,6 +42,9 @@ require_relative "nodes/with_exp"
|
|
42
42
|
require_relative "nodes/with_spec"
|
43
43
|
require_relative "nodes/name_intro"
|
44
44
|
require_relative "nodes/where_clause"
|
45
|
+
require_relative "nodes/join"
|
45
46
|
require_relative "nodes/cross_join"
|
46
47
|
require_relative "nodes/inner_join"
|
48
|
+
require_relative "nodes/left_join"
|
47
49
|
require_relative "nodes/summarizer"
|
50
|
+
require_relative "nodes/func_call"
|
@@ -24,10 +24,13 @@ rules:
|
|
24
24
|
join_exp:
|
25
25
|
- cross_join
|
26
26
|
- inner_join
|
27
|
+
- left_join
|
27
28
|
cross_join:
|
28
29
|
- [ table_spec, table_spec ]
|
29
30
|
inner_join:
|
30
31
|
- [ table_spec, table_spec, predicate ]
|
32
|
+
left_join:
|
33
|
+
- [ table_spec, table_spec, predicate ]
|
31
34
|
using:
|
32
35
|
- [ column_name+ ]
|
33
36
|
select_exp:
|
@@ -76,6 +79,7 @@ rules:
|
|
76
79
|
- qualified_name
|
77
80
|
- column_name
|
78
81
|
- summarizer
|
82
|
+
- func_call
|
79
83
|
- literal
|
80
84
|
a_name:
|
81
85
|
- qualified_name
|
@@ -88,6 +92,10 @@ rules:
|
|
88
92
|
- [ summary_func, qualified_name ]
|
89
93
|
summary_func:
|
90
94
|
- "::Symbol"
|
95
|
+
func_call:
|
96
|
+
- [ func_name, scalar_exp+ ]
|
97
|
+
func_name:
|
98
|
+
- "::Symbol"
|
91
99
|
table_name:
|
92
100
|
- [ name_rgx ]
|
93
101
|
range_var_name:
|
@@ -96,6 +104,8 @@ rules:
|
|
96
104
|
- [ integer ]
|
97
105
|
offset_clause:
|
98
106
|
- [ integer ]
|
107
|
+
default_right_tuple:
|
108
|
+
- "::Hash"
|
99
109
|
integer:
|
100
110
|
- "::Integer"
|
101
111
|
literal:
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Bmg
|
2
|
+
module Sql
|
3
|
+
module FuncCall
|
4
|
+
include Expr
|
5
|
+
|
6
|
+
def func_name
|
7
|
+
self[1]
|
8
|
+
end
|
9
|
+
|
10
|
+
def func_args
|
11
|
+
self[2..-1]
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_sql(buffer, dialect)
|
15
|
+
buffer << summary_name.upcase << "("
|
16
|
+
buffer << func_args.map{|fa| fa.to_sql("", dialect) }.join(', ')
|
17
|
+
buffer << ")"
|
18
|
+
buffer
|
19
|
+
end
|
20
|
+
|
21
|
+
end # module FuncCall
|
22
|
+
end # module Sql
|
23
|
+
end # module Bmg
|
@@ -2,34 +2,12 @@ module Bmg
|
|
2
2
|
module Sql
|
3
3
|
module InnerJoin
|
4
4
|
include Expr
|
5
|
+
include Join
|
5
6
|
|
6
7
|
INNER = "INNER".freeze
|
7
|
-
JOIN = "JOIN".freeze
|
8
|
-
ON = "ON".freeze
|
9
8
|
|
10
|
-
def
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
def left
|
15
|
-
self[1]
|
16
|
-
end
|
17
|
-
|
18
|
-
def right
|
19
|
-
self[2]
|
20
|
-
end
|
21
|
-
|
22
|
-
def predicate
|
23
|
-
last
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_sql(buffer, dialect)
|
27
|
-
left.to_sql(buffer, dialect)
|
28
|
-
buffer << SPACE << JOIN << SPACE
|
29
|
-
right.to_sql(buffer, dialect)
|
30
|
-
buffer << SPACE << ON << SPACE
|
31
|
-
predicate.to_sql(buffer, dialect)
|
32
|
-
buffer
|
9
|
+
def type
|
10
|
+
INNER
|
33
11
|
end
|
34
12
|
|
35
13
|
end # module InnerJoin
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Bmg
|
2
|
+
module Sql
|
3
|
+
module Join
|
4
|
+
include Expr
|
5
|
+
|
6
|
+
JOIN = "JOIN".freeze
|
7
|
+
ON = "ON".freeze
|
8
|
+
|
9
|
+
def type
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def join?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
def left
|
18
|
+
self[1]
|
19
|
+
end
|
20
|
+
|
21
|
+
def right
|
22
|
+
self[2]
|
23
|
+
end
|
24
|
+
|
25
|
+
def predicate
|
26
|
+
last
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_sql(buffer, dialect)
|
30
|
+
left.to_sql(buffer, dialect)
|
31
|
+
if type.nil?
|
32
|
+
buffer << SPACE << JOIN << SPACE
|
33
|
+
else
|
34
|
+
buffer << SPACE << TYPE << SPACE << JOIN << SPACE
|
35
|
+
end
|
36
|
+
right.to_sql(buffer, dialect)
|
37
|
+
buffer << SPACE << ON << SPACE
|
38
|
+
predicate.to_sql(buffer, dialect)
|
39
|
+
buffer
|
40
|
+
end
|
41
|
+
|
42
|
+
end # module Join
|
43
|
+
end # module Sql
|
44
|
+
end # module Bmg
|
data/lib/bmg/sql/processor.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Bmg
|
2
|
+
module Sql
|
3
|
+
class Processor
|
4
|
+
class Bind < Processor
|
5
|
+
|
6
|
+
def initialize(binding, builder)
|
7
|
+
super(builder)
|
8
|
+
@binding = binding
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_select_exp(sexpr)
|
12
|
+
if w = sexpr.where_clause
|
13
|
+
pred = Predicate::Grammar.sexpr(w.predicate.bind(@binding))
|
14
|
+
sexpr.with_update(:where_clause, [ :where_clause, pred ])
|
15
|
+
else
|
16
|
+
sexpr
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end # class Bind
|
21
|
+
end # class Processor
|
22
|
+
end # module Sql
|
23
|
+
end # module Bmg
|
@@ -4,18 +4,19 @@ module Bmg
|
|
4
4
|
class Join < Processor
|
5
5
|
include JoinSupport
|
6
6
|
|
7
|
-
def initialize(right, on, builder)
|
7
|
+
def initialize(right, on, options, builder)
|
8
8
|
super(builder)
|
9
9
|
@right = right
|
10
10
|
@on = on
|
11
|
+
@options = options
|
11
12
|
end
|
12
|
-
attr_reader :right, :on
|
13
|
+
attr_reader :right, :on, :options
|
13
14
|
|
14
15
|
def call(sexpr)
|
15
16
|
if unjoinable?(sexpr)
|
16
17
|
call(builder.from_self(sexpr))
|
17
18
|
elsif unjoinable?(right)
|
18
|
-
Join.new(builder.from_self(right), on, builder).call(sexpr)
|
19
|
+
Join.new(builder.from_self(right), on, options, builder).call(sexpr)
|
19
20
|
else
|
20
21
|
super(sexpr)
|
21
22
|
end
|
@@ -45,14 +46,21 @@ module Bmg
|
|
45
46
|
left_list, right_list = left.select_list, right.select_list
|
46
47
|
list = left_list.dup
|
47
48
|
right_list.each_child do |child, index|
|
48
|
-
|
49
|
+
next if left_list.knows?(child.as_name)
|
50
|
+
if left_join?
|
51
|
+
list << coalesced(child)
|
52
|
+
else
|
53
|
+
list << child
|
54
|
+
end
|
49
55
|
end
|
50
56
|
list
|
51
57
|
end
|
52
58
|
|
53
59
|
def join_from_clauses(left, right)
|
54
60
|
joincon = join_predicate(left, right, on)
|
55
|
-
join = if
|
61
|
+
join = if left_join?
|
62
|
+
[:left_join, left.table_spec, right.table_spec, joincon]
|
63
|
+
elsif joincon.tautology?
|
56
64
|
[:cross_join, left.table_spec, right.table_spec]
|
57
65
|
else
|
58
66
|
[:inner_join, left.table_spec, right.table_spec, joincon]
|
@@ -75,6 +83,26 @@ module Bmg
|
|
75
83
|
order_by.first + order_by.last.sexpr_body
|
76
84
|
end
|
77
85
|
|
86
|
+
private
|
87
|
+
|
88
|
+
def left_join?
|
89
|
+
options[:kind] == :left
|
90
|
+
end
|
91
|
+
|
92
|
+
def coalesced(child)
|
93
|
+
drt, as_name = options[:default_right_tuple], child.as_name.to_sym
|
94
|
+
if drt && drt.has_key?(as_name)
|
95
|
+
child.with_update(1, [
|
96
|
+
:func_call,
|
97
|
+
:coalesce,
|
98
|
+
child.left,
|
99
|
+
[:literal, drt[as_name]]
|
100
|
+
])
|
101
|
+
else
|
102
|
+
child
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
78
106
|
end # class Join
|
79
107
|
end # class Processor
|
80
108
|
end # module Sql
|
@@ -39,8 +39,8 @@ 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
|
-
left = Predicate.new(Predicate::Grammar.sexpr(sexpr.predicate))
|
43
|
-
right = Predicate.new(Predicate::Grammar.sexpr(@predicate.sexpr))
|
42
|
+
left = Predicate.new(Predicate::Grammar.sexpr(sexpr.predicate)).unqualify
|
43
|
+
right = Predicate.new(Predicate::Grammar.sexpr(@predicate.sexpr)).unqualify
|
44
44
|
return (left & right).contradiction?
|
45
45
|
end
|
46
46
|
|
data/lib/bmg/sql/relation.rb
CHANGED
@@ -18,6 +18,12 @@ module Bmg
|
|
18
18
|
|
19
19
|
public
|
20
20
|
|
21
|
+
def bind(binding)
|
22
|
+
expr = before_use(self.expr)
|
23
|
+
expr = Processor::Bind.new(binding, builder).call(expr)
|
24
|
+
_instance(type, builder, expr)
|
25
|
+
end
|
26
|
+
|
21
27
|
def each(&bl)
|
22
28
|
raise NotImplementedError
|
23
29
|
end
|
@@ -56,7 +62,21 @@ module Bmg
|
|
56
62
|
if right_expr = extract_compatible_sexpr(right)
|
57
63
|
right_expr = Processor::Requalify.new(builder).call(right_expr)
|
58
64
|
expr = before_use(self.expr)
|
59
|
-
expr = Processor::Join.new(right_expr, on, builder).call(expr)
|
65
|
+
expr = Processor::Join.new(right_expr, on, {}, builder).call(expr)
|
66
|
+
_instance(type, builder, expr)
|
67
|
+
else
|
68
|
+
super
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def _left_join(type, right, on, default_right_tuple)
|
73
|
+
if right_expr = extract_compatible_sexpr(right)
|
74
|
+
right_expr = Processor::Requalify.new(builder).call(right_expr)
|
75
|
+
expr = before_use(self.expr)
|
76
|
+
expr = Processor::Join.new(right_expr, on, {
|
77
|
+
kind: :left,
|
78
|
+
default_right_tuple: default_right_tuple
|
79
|
+
}, builder).call(expr)
|
60
80
|
_instance(type, builder, expr)
|
61
81
|
else
|
62
82
|
super
|
@@ -23,14 +23,14 @@ module Bmg
|
|
23
23
|
# Generates a relationally equivalent list of (type,table,predicate)
|
24
24
|
# triplets, where:
|
25
25
|
#
|
26
|
-
# - type is :base, :cross_join or :
|
26
|
+
# - type is :base, :cross_join, :inner_join, or :left_join
|
27
27
|
# - table is table_as, native_table_as or subquery_as
|
28
28
|
# - predicate is a join predicate `ti.attri = tj.attrj AND ...`
|
29
29
|
#
|
30
30
|
# So that
|
31
31
|
#
|
32
32
|
# 1) the types are observed in strict increasing order (one :base, zero
|
33
|
-
# or more :cross_join, zero or more :inner_join)
|
33
|
+
# or more :cross_join, zero or more :inner_join, zero or more :left_join)
|
34
34
|
#
|
35
35
|
# 2) the list is such that it can be safely written as an expression
|
36
36
|
# of the following SQL syntax:
|
@@ -40,6 +40,7 @@ module Bmg
|
|
40
40
|
# cross_join t3 # [ :cross_join, t3, nil ]
|
41
41
|
# inner_join t4 ON p4 # [ :inner_join, t4, p4 ]
|
42
42
|
# inner_join t5 ON p5 # [ :inner_join, t5, p5 ]
|
43
|
+
# left_join t6 ON p6 # [ :left_join, t6, p6 ]
|
43
44
|
#
|
44
45
|
# that is, the linearization is correct only if each predicate `pi`
|
45
46
|
# only makes reference to tables introduced before it (no forward
|
@@ -87,13 +88,18 @@ module Bmg
|
|
87
88
|
# reference to tables not introduced yet)
|
88
89
|
#
|
89
90
|
def order_all(tables, joins)
|
90
|
-
# Our first strategy is simple: let sort the tables by moving the
|
91
|
-
#
|
92
|
-
# will yield the base an cross
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
# Our first strategy is simple: let sort the tables by moving the
|
92
|
+
# all left joins at the end, and all not referenced in join clauses
|
93
|
+
# at the beginning of the list => they will yield the base an cross
|
94
|
+
# join clauses first.
|
95
|
+
tables = tables.sort{|(t1,k1),(t2,k2)|
|
96
|
+
if k1 == :left_join || k2 == :left_join
|
97
|
+
k1 == k2 ? 0 : (k1 == :left_join ? 1 : -1)
|
98
|
+
else
|
99
|
+
t1js = joins.select{|j| uses?(j, t1) }.size
|
100
|
+
t2js = joins.select{|j| uses?(j, t2) }.size
|
101
|
+
t1js == 0 ? (t2js == 0 ? 0 : -1) : (t2js == 0 ? 1 : 0)
|
102
|
+
end
|
97
103
|
}
|
98
104
|
|
99
105
|
# Then order all recursively in that order of tables, filling a result
|
@@ -119,7 +125,15 @@ module Bmg
|
|
119
125
|
|
120
126
|
# Decide which kind of join it is, according to the result and
|
121
127
|
# the number of join clauses that will be used
|
122
|
-
join_kind = result.empty?
|
128
|
+
join_kind = if result.empty?
|
129
|
+
:base
|
130
|
+
elsif table.last == :left_join
|
131
|
+
:left_join
|
132
|
+
elsif on.empty?
|
133
|
+
:cross_join
|
134
|
+
else
|
135
|
+
:inner_join
|
136
|
+
end
|
123
137
|
|
124
138
|
# Compute the AND([eq]) predicate on selected join clauses
|
125
139
|
predicate = on.inject(nil){|p,clause|
|
@@ -127,7 +141,7 @@ module Bmg
|
|
127
141
|
}
|
128
142
|
|
129
143
|
# Recurse with that new clause in the result
|
130
|
-
clause = [ join_kind, table, predicate ]
|
144
|
+
clause = [ join_kind, table[0], predicate ]
|
131
145
|
_order_all(tables_tail, joins_tail, result + [clause])
|
132
146
|
end
|
133
147
|
end
|
@@ -139,13 +153,13 @@ module Bmg
|
|
139
153
|
# `ti` and making no reference to non introduced tables, and the others.
|
140
154
|
def split_joins(joins, table, tables_tail)
|
141
155
|
joins.partition{|j|
|
142
|
-
uses?(j, table) && !tables_tail.find{|t|
|
143
|
-
uses?(j, t)
|
156
|
+
uses?(j, table[0]) && !tables_tail.find{|t|
|
157
|
+
uses?(j, t[0])
|
144
158
|
}
|
145
159
|
}
|
146
160
|
end
|
147
161
|
|
148
|
-
# Returns whether the join
|
162
|
+
# Returns whether the join conditions references the given table
|
149
163
|
def uses?(condition, table)
|
150
164
|
name = table.as_name.to_s
|
151
165
|
left_name = var_name(condition[1])
|
@@ -172,23 +186,27 @@ module Bmg
|
|
172
186
|
def collect(sexpr)
|
173
187
|
tables = []
|
174
188
|
joins = []
|
175
|
-
_collect(sexpr, tables, joins)
|
189
|
+
_collect(sexpr, tables, joins, :base)
|
176
190
|
[ tables, joins ]
|
177
191
|
end
|
178
192
|
|
179
|
-
def _collect(sexpr, tables, joins)
|
193
|
+
def _collect(sexpr, tables, joins, kind)
|
180
194
|
case sexpr.first
|
181
195
|
when :from_clause
|
182
|
-
_collect(sexpr.table_spec, tables, joins)
|
196
|
+
_collect(sexpr.table_spec, tables, joins, kind)
|
183
197
|
when :table_as, :native_table_as, :subquery_as
|
184
|
-
tables << sexpr
|
198
|
+
tables << [sexpr, kind]
|
199
|
+
when :cross_join
|
200
|
+
_collect(sexpr.left, tables, joins, :cross_join)
|
201
|
+
_collect(sexpr.right, tables, joins, :cross_join)
|
185
202
|
when :inner_join
|
186
203
|
_collect_joins(sexpr.predicate, joins)
|
187
|
-
_collect(sexpr.left, tables, joins)
|
188
|
-
_collect(sexpr.right, tables, joins)
|
189
|
-
when :
|
190
|
-
|
191
|
-
_collect(sexpr.
|
204
|
+
_collect(sexpr.left, tables, joins, :inner_join)
|
205
|
+
_collect(sexpr.right, tables, joins, :inner_join)
|
206
|
+
when :left_join
|
207
|
+
_collect_joins(sexpr.predicate, joins)
|
208
|
+
_collect(sexpr.left, tables, joins, kind)
|
209
|
+
_collect(sexpr.right, tables, joins, :left_join)
|
192
210
|
end
|
193
211
|
end
|
194
212
|
|
data/lib/bmg/type.rb
CHANGED
@@ -175,6 +175,15 @@ module Bmg
|
|
175
175
|
}
|
176
176
|
end
|
177
177
|
|
178
|
+
def left_join(right, on, default_right_tuple)
|
179
|
+
join_compatible!(right, on) if typechecked? && knows_attrlist?
|
180
|
+
dup.tap{|x|
|
181
|
+
x.attrlist = (knows_attrlist? and right.knows_attrlist?) ? (self.attrlist + right.attrlist).uniq : nil
|
182
|
+
x.predicate = Predicate.tautology
|
183
|
+
x.keys = nil
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
178
187
|
def matching(right, on)
|
179
188
|
join_compatible!(right, on) if typechecked? && knows_attrlist?
|
180
189
|
self
|
data/lib/bmg/version.rb
CHANGED
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.
|
4
|
+
version: 0.17.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: 2020-
|
11
|
+
date: 2020-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: predicate
|
@@ -16,20 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '2.
|
19
|
+
version: '2.3'
|
20
20
|
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 2.
|
22
|
+
version: 2.3.3
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - "~>"
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '2.
|
29
|
+
version: '2.3'
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 2.
|
32
|
+
version: 2.3.3
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: rake
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -194,9 +194,12 @@ files:
|
|
194
194
|
- lib/bmg/sql/nodes/except.rb
|
195
195
|
- lib/bmg/sql/nodes/expr.rb
|
196
196
|
- lib/bmg/sql/nodes/from_clause.rb
|
197
|
+
- lib/bmg/sql/nodes/func_call.rb
|
197
198
|
- lib/bmg/sql/nodes/group_by_clause.rb
|
198
199
|
- lib/bmg/sql/nodes/inner_join.rb
|
199
200
|
- lib/bmg/sql/nodes/intersect.rb
|
201
|
+
- lib/bmg/sql/nodes/join.rb
|
202
|
+
- lib/bmg/sql/nodes/left_join.rb
|
200
203
|
- lib/bmg/sql/nodes/limit_clause.rb
|
201
204
|
- lib/bmg/sql/nodes/literal.rb
|
202
205
|
- lib/bmg/sql/nodes/name_intro.rb
|
@@ -222,6 +225,7 @@ files:
|
|
222
225
|
- lib/bmg/sql/nodes/with_spec.rb
|
223
226
|
- lib/bmg/sql/processor.rb
|
224
227
|
- lib/bmg/sql/processor/all.rb
|
228
|
+
- lib/bmg/sql/processor/bind.rb
|
225
229
|
- lib/bmg/sql/processor/clip.rb
|
226
230
|
- lib/bmg/sql/processor/constants.rb
|
227
231
|
- lib/bmg/sql/processor/distinct.rb
|
@@ -278,8 +282,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
278
282
|
- !ruby/object:Gem::Version
|
279
283
|
version: '0'
|
280
284
|
requirements: []
|
281
|
-
|
282
|
-
rubygems_version: 2.7.6
|
285
|
+
rubygems_version: 3.1.2
|
283
286
|
signing_key:
|
284
287
|
specification_version: 4
|
285
288
|
summary: Bmg is Alf's successor.
|