bmg 0.17.1 → 0.17.6
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/README.md +73 -5
- data/lib/bmg.rb +2 -0
- data/lib/bmg/algebra.rb +23 -0
- data/lib/bmg/algebra/shortcuts.rb +6 -0
- data/lib/bmg/operator.rb +1 -0
- data/lib/bmg/operator/join.rb +24 -4
- data/lib/bmg/operator/transform.rb +57 -0
- data/lib/bmg/relation.rb +13 -0
- data/lib/bmg/relation/proxy.rb +63 -0
- data/lib/bmg/sequel/translator.rb +37 -12
- data/lib/bmg/sql/grammar.rb +3 -0
- data/lib/bmg/sql/grammar.sexp.yml +10 -0
- data/lib/bmg/sql/nodes/column_name.rb +4 -0
- data/lib/bmg/sql/nodes/cross_join.rb +1 -12
- data/lib/bmg/sql/nodes/func_call.rb +27 -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/nodes/literal.rb +4 -0
- data/lib/bmg/sql/nodes/qualified_name.rb +4 -0
- data/lib/bmg/sql/nodes/select_exp.rb +4 -0
- data/lib/bmg/sql/nodes/select_item.rb +4 -0
- data/lib/bmg/sql/nodes/select_list.rb +4 -0
- data/lib/bmg/sql/nodes/select_star.rb +4 -0
- data/lib/bmg/sql/nodes/summarizer.rb +4 -0
- data/lib/bmg/sql/processor/join.rb +33 -5
- data/lib/bmg/sql/processor/where.rb +3 -3
- data/lib/bmg/sql/relation.rb +15 -1
- data/lib/bmg/sql/support/from_clause_orderer.rb +41 -23
- data/lib/bmg/support.rb +1 -0
- data/lib/bmg/support/keys.rb +4 -0
- data/lib/bmg/support/tuple_transformer.rb +62 -0
- data/lib/bmg/type.rb +34 -0
- data/lib/bmg/version.rb +1 -1
- data/lib/bmg/writer.rb +1 -0
- data/lib/bmg/writer/csv.rb +31 -0
- metadata +28 -20
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,27 @@
|
|
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 is_computed?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_sql(buffer, dialect)
|
19
|
+
buffer << summary_name.upcase << "("
|
20
|
+
buffer << func_args.map{|fa| fa.to_sql("", dialect) }.join(', ')
|
21
|
+
buffer << ")"
|
22
|
+
buffer
|
23
|
+
end
|
24
|
+
|
25
|
+
end # module FuncCall
|
26
|
+
end # module Sql
|
27
|
+
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
|
@@ -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
|
@@ -20,7 +20,7 @@ module Bmg
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def on_select_exp(sexpr)
|
23
|
-
if sexpr.group_by_clause
|
23
|
+
if sexpr.group_by_clause || sexpr.has_computed_attributes?
|
24
24
|
sexpr = builder.from_self(sexpr)
|
25
25
|
call(sexpr)
|
26
26
|
else
|
@@ -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
@@ -62,7 +62,21 @@ module Bmg
|
|
62
62
|
if right_expr = extract_compatible_sexpr(right)
|
63
63
|
right_expr = Processor::Requalify.new(builder).call(right_expr)
|
64
64
|
expr = before_use(self.expr)
|
65
|
-
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)
|
66
80
|
_instance(type, builder, expr)
|
67
81
|
else
|
68
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
|
|