bmg 0.23.6 → 0.24.1

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
  SHA256:
3
- metadata.gz: a4cb86461e5f53734d08dcb3668bd2a99630680d7213bb946fa5a13cbf0f86f2
4
- data.tar.gz: f9a80e014cb2769d6d8da8cea581b18c7601c2d1015db18e1eac6333f8e08876
3
+ metadata.gz: ff9e5fa48bb225feaeee07ddbfb15f8616e7943be2304113d727d7b8fd71dda2
4
+ data.tar.gz: b8924512fc6e1b4d8cdb6d86ab40e5e5f317850839ae6d8bc3f02ddd8534e578
5
5
  SHA512:
6
- metadata.gz: 7621e13e58615206096b3f2d2799a79aa4e662592688b295329bf09626efe86b9a8f4f6f01aba5432513e6054c7568894f13dfccd86d8932f28fa1e9b0892a28
7
- data.tar.gz: 9cfc8e393267db43d44764db894b47639d6cfc445b3e7d80e1287929e02cf8ec4d7e541432f52905002953ac727770b5c30b3c4c850ff1a039d98beba2811469
6
+ metadata.gz: dc7c8dde953a4792a17177abe5a3decd4a3aac156cda87c7ad8854382d2248b03a272a6d318a348fbcba255ba5302e5adbaa4f3764c6db04b61924e9d11e50f4
7
+ data.tar.gz: 5148e32c981959c62eedae0f8b15cd8816d9afc01f7e1e00e94cc4d7086892e22dc4b6da0a3bb07a57ec138e91060b3c2a839c9f6e4ef5b6af053df08382c20e
@@ -70,13 +70,21 @@ 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, :func_call
73
+ when :literal, :summarizer, :func_call, :case_when
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_case_when(sexpr)
81
+ operand = apply(sexpr[1])
82
+ mapping = sexpr[2..-1].each_slice(2).each_with_object({}){|(w, t), h|
83
+ h[w.last] = t.last
84
+ }
85
+ ::Sequel.case(mapping, nil, operand)
86
+ end
87
+
80
88
  def on_func_call(sexpr)
81
89
  case sexpr.func_name
82
90
  when :cast
@@ -48,3 +48,4 @@ require_relative "nodes/inner_join"
48
48
  require_relative "nodes/left_join"
49
49
  require_relative "nodes/summarizer"
50
50
  require_relative "nodes/func_call"
51
+ require_relative "nodes/case_when"
@@ -81,6 +81,9 @@ rules:
81
81
  - summarizer
82
82
  - func_call
83
83
  - literal
84
+ - case_when
85
+ case_when:
86
+ - [ scalar_exp, literal+ ]
84
87
  a_name:
85
88
  - qualified_name
86
89
  - column_name
@@ -0,0 +1,33 @@
1
+ module Bmg
2
+ module Sql
3
+ module CaseWhen
4
+ include Expr
5
+
6
+ def operand
7
+ self[1]
8
+ end
9
+
10
+ def whens
11
+ self[2..-1].each_slice(2).to_a
12
+ end
13
+
14
+ def is_computed?
15
+ true
16
+ end
17
+
18
+ def to_sql(buffer, dialect)
19
+ buffer << "CASE "
20
+ operand.to_sql(buffer, dialect)
21
+ whens.each do |(when_val, then_val)|
22
+ buffer << " WHEN "
23
+ when_val.to_sql(buffer, dialect)
24
+ buffer << " THEN "
25
+ then_val.to_sql(buffer, dialect)
26
+ end
27
+ buffer << " END"
28
+ buffer
29
+ end
30
+
31
+ end # module CaseWhen
32
+ end # module Sql
33
+ end # module Bmg
@@ -15,7 +15,7 @@ module Bmg
15
15
  def call(sexpr)
16
16
  if unjoinable?(sexpr)
17
17
  call(builder.from_self(sexpr))
18
- elsif unjoinable?(right)
18
+ elsif unjoinable?(right) || left_join_over_restricted_right?
19
19
  Join.new(builder.from_self(right), on, options, builder).call(sexpr)
20
20
  else
21
21
  super(sexpr)
@@ -37,6 +37,20 @@ module Bmg
37
37
  sexpr.set_operator? or sexpr.limit_or_offset? or sexpr.group_by?
38
38
  end
39
39
 
40
+ # A restriction carried by the right operand of a left join may not be
41
+ # merged into the WHERE of the whole expression: there it would also
42
+ # have to hold of the tuples the left join fills with nulls, which is an
43
+ # inner join and loses exactly what the left join is for. Kept inside a
44
+ # derived table instead, where it applies to the right operand alone.
45
+ #
46
+ # The join condition would hold it just as well, and with less SQL, but
47
+ # it is the one place a restriction may be anything -- `>`, `IN`, a
48
+ # negation -- while the from clause orderer models a join condition as
49
+ # equalities between two qualified names.
50
+ def left_join_over_restricted_right?
51
+ left_join? && !right.select_exp.predicate.nil?
52
+ end
53
+
40
54
  def join_set_quantifiers(left, right)
41
55
  left_q, right_q = left.set_quantifier, right.set_quantifier
42
56
  left_q == right_q ? left_q : builder.distinct
@@ -50,6 +50,14 @@ module Bmg
50
50
  SplitSupported.split_supported(*args, &bl)
51
51
  end
52
52
 
53
+ # An SQL-able element the transform can emit verbatim: an object that
54
+ # carries its own SQL (the predicate gem's `:sql`/`:sql_literal`
55
+ # convention) or a Sequel expression (rendered through `Sequel.expr`).
56
+ def self.sql_expr?(x)
57
+ x.respond_to?(:sql_literal) || x.respond_to?(:sql) ||
58
+ (defined?(::Sequel::SQL::Expression) && x.is_a?(::Sequel::SQL::Expression))
59
+ end
60
+
53
61
  def on_select_list(sexpr)
54
62
  sexpr.each_with_index.map{|child,index|
55
63
  index == 0 ? child : apply(child)
@@ -66,8 +74,17 @@ module Bmg
66
74
  func_call_node(sexpr, Array(t).reverse),
67
75
  sexpr[2]
68
76
  ])
77
+ when Hash
78
+ sexpr([:select_item,
79
+ case_when_node(sexpr, t),
80
+ sexpr[2]
81
+ ])
69
82
  else
70
- raise NotSupportedError
83
+ # An SQL-able value (e.g. a Sequel expression) carries its own SQL
84
+ # — including the column reference — so it rides through as a
85
+ # literal, which the Sequel translator emits verbatim.
86
+ raise NotSupportedError unless Transform.sql_expr?(t)
87
+ sexpr([:select_item, [:literal, t], sexpr[2]])
71
88
  end
72
89
  end
73
90
 
@@ -89,6 +106,15 @@ module Bmg
89
106
  [ :literal, head ] ]
90
107
  end
91
108
 
109
+ def case_when_node(sexpr, mapping)
110
+ node = [:case_when, sexpr[1]]
111
+ mapping.each do |when_val, then_val|
112
+ node << [:literal, when_val]
113
+ node << [:literal, then_val]
114
+ end
115
+ node
116
+ end
117
+
92
118
  def transformation_for(as)
93
119
  case t = transformation
94
120
  when Class then t
@@ -167,7 +167,8 @@ module Bmg
167
167
  def _transform(type, transformation, options)
168
168
  expr = before_use(self.expr)
169
169
  sup, unsup = Processor::Transform.split_supported(transformation){|x|
170
- [String, Integer, Float, Date, DateTime].include?(x)
170
+ [String, Integer, Float, Date, DateTime].include?(x) || x.is_a?(Hash) ||
171
+ Processor::Transform.sql_expr?(x)
171
172
  }
172
173
  return super if sup.nil?
173
174
  expr = Processor::Transform.new(sup, options, builder).call(expr)
data/lib/bmg/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 23
5
- TINY = 6
4
+ MINOR = 24
5
+ TINY = 1
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bmg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.6
4
+ version: 0.24.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-10-04 00:00:00.000000000 Z
10
+ date: 2026-10-03 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: predicate
@@ -233,6 +232,7 @@ files:
233
232
  - lib/bmg/sql/ext/predicate/tautology.rb
234
233
  - lib/bmg/sql/grammar.rb
235
234
  - lib/bmg/sql/grammar.sexp.yml
235
+ - lib/bmg/sql/nodes/case_when.rb
236
236
  - lib/bmg/sql/nodes/column_name.rb
237
237
  - lib/bmg/sql/nodes/cross_join.rb
238
238
  - lib/bmg/sql/nodes/except.rb
@@ -332,7 +332,6 @@ homepage: http://github.com/enspirit/bmg
332
332
  licenses:
333
333
  - MIT
334
334
  metadata: {}
335
- post_install_message:
336
335
  rdoc_options: []
337
336
  require_paths:
338
337
  - lib
@@ -347,8 +346,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
347
346
  - !ruby/object:Gem::Version
348
347
  version: '0'
349
348
  requirements: []
350
- rubygems_version: 3.4.19
351
- signing_key:
349
+ rubygems_version: 3.6.2
352
350
  specification_version: 4
353
351
  summary: Bmg is Alf's successor.
354
352
  test_files: []