bmg 0.17.1 → 0.17.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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:
@@ -15,6 +15,10 @@ module Bmg
15
15
  last
16
16
  end
17
17
 
18
+ def is_computed?
19
+ false
20
+ end
21
+
18
22
  def to_sql(buffer, dialect)
19
23
  buffer << dialect.quote_identifier(last)
20
24
  buffer
@@ -2,18 +2,7 @@ module Bmg
2
2
  module Sql
3
3
  module CrossJoin
4
4
  include Expr
5
-
6
- def join?
7
- true
8
- end
9
-
10
- def left
11
- self[1]
12
- end
13
-
14
- def right
15
- self[2]
16
- end
5
+ include Join
17
6
 
18
7
  def to_sql(buffer, dialect)
19
8
  each_child do |child, index|
@@ -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 join?
11
- true
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
@@ -0,0 +1,15 @@
1
+ module Bmg
2
+ module Sql
3
+ module LeftJoin
4
+ include Expr
5
+ include Join
6
+
7
+ LEFT = "LEFT".freeze
8
+
9
+ def type
10
+ LEFT
11
+ end
12
+
13
+ end # module LeftJoin
14
+ end # module Sql
15
+ end # module Bmg
@@ -8,6 +8,10 @@ module Bmg
8
8
  nil
9
9
  end
10
10
 
11
+ def is_computed?
12
+ false
13
+ end
14
+
11
15
  def to_sql(buffer, dialect)
12
16
  to_sql_literal(buffer, last)
13
17
  buffer
@@ -20,6 +20,10 @@ module Bmg
20
20
  as_name
21
21
  end
22
22
 
23
+ def is_computed?
24
+ false
25
+ end
26
+
23
27
  def to_sql(buffer, dialect)
24
28
  self[1].to_sql(buffer, dialect)
25
29
  buffer << '.'
@@ -27,6 +27,10 @@ module Bmg
27
27
  set_quantifier.all?
28
28
  end
29
29
 
30
+ def has_computed_attributes?
31
+ select_list.has_computed_attributes?
32
+ end
33
+
30
34
  def join?
31
35
  from_clause && from_clause.join?
32
36
  end
@@ -23,6 +23,10 @@ module Bmg
23
23
  last.as_name
24
24
  end
25
25
 
26
+ def is_computed?
27
+ left.is_computed?
28
+ end
29
+
26
30
  def to_sql(buffer, dialect)
27
31
  self[1].to_sql(buffer, dialect)
28
32
  unless would_be_name == as_name
@@ -23,6 +23,10 @@ module Bmg
23
23
  Builder::IS_TABLE_DEE == self
24
24
  end
25
25
 
26
+ def has_computed_attributes?
27
+ sexpr_body.any?{|item| item.is_computed? }
28
+ end
29
+
26
30
  def knows?(as_name)
27
31
  find_child{|child| child.as_name == as_name }
28
32
  end
@@ -11,6 +11,10 @@ module Bmg
11
11
  }
12
12
  end
13
13
 
14
+ def has_computed_attributes?
15
+ false
16
+ end
17
+
14
18
  def to_sql(buffer, dialect)
15
19
  last.to_sql(buffer, dialect)
16
20
  buffer << DOT << STAR
@@ -11,6 +11,10 @@ module Bmg
11
11
  self.last
12
12
  end
13
13
 
14
+ def is_computed?
15
+ true
16
+ end
17
+
14
18
  def to_sql(buffer, dialect)
15
19
  buffer << summary_func.upcase << "("
16
20
  summary_expr.to_sql(buffer, dialect)
@@ -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
- list << child unless left_list.knows?(child.as_name)
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 joincon.tautology?
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
 
@@ -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 :inner_join
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 ones
91
- # not referenced in join clauses at the beginning of the list => they
92
- # will yield the base an cross join clauses first.
93
- tables = tables.sort{|t1,t2|
94
- t1js = joins.select{|j| uses?(j, t1) }.size
95
- t2js = joins.select{|j| uses?(j, t2) }.size
96
- t1js == 0 ? (t2js == 0 ? 0 : -1) : (t2js == 0 ? 1 : 0)
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? ? :base : (on.empty? ? :cross_join : :inner_join)
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 :cross_join
190
- _collect(sexpr.left, tables, joins)
191
- _collect(sexpr.right, tables, joins)
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