bmg 0.23.6 → 1.0.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
  SHA256:
3
- metadata.gz: a4cb86461e5f53734d08dcb3668bd2a99630680d7213bb946fa5a13cbf0f86f2
4
- data.tar.gz: f9a80e014cb2769d6d8da8cea581b18c7601c2d1015db18e1eac6333f8e08876
3
+ metadata.gz: b73ed9026efe140b3d55eb59a1949db5fad0ac04ff401f3c44199d39274108ca
4
+ data.tar.gz: 1d375d5ca7638e43d21c6bc27a34b3df47f6947d7d853158fa0bdc90db0754cc
5
5
  SHA512:
6
- metadata.gz: 7621e13e58615206096b3f2d2799a79aa4e662592688b295329bf09626efe86b9a8f4f6f01aba5432513e6054c7568894f13dfccd86d8932f28fa1e9b0892a28
7
- data.tar.gz: 9cfc8e393267db43d44764db894b47639d6cfc445b3e7d80e1287929e02cf8ec4d7e541432f52905002953ac727770b5c30b3c4c850ff1a039d98beba2811469
6
+ metadata.gz: f55dba1dc2db2ef68beaf1d0f58c0c84fd2ff7c4bb2c1b883ad5726fcdd6b44588529f602876783d6d14823e3cb6494bb9335490c6b214cc725a1e55d93d4181
7
+ data.tar.gz: ba1fe4d29f70002410ddb0d7afe7fd63555a87746cbfac7c85bc1ca0c3af5c796a3f4245928e8c1f841dfc847ad375518728c1f3ad225c434bb2c8420f8e4a2b
data/README.md CHANGED
@@ -31,6 +31,7 @@ further down this README.
31
31
  * [How is this different?](#how-is-this-different)
32
32
  * [... from similar libraries](#-from-similar-libraries)
33
33
  * [... from Alf](#-from-alf)
34
+ * [Stability and versioning](#stability-and-versioning)
34
35
  * [Contribute](#contribute)
35
36
  * [License](#license)
36
37
 
@@ -202,7 +203,7 @@ documentation.
202
203
 
203
204
  ### Connecting to Redis databases
204
205
 
205
- Bmg currently requires `bmg-redis` and `redis >= 4.6` to connect
206
+ Bmg currently requires `bmg-redis` and `redis >= 4.0` to connect
206
207
  to Redis databases. You also need to require `bmg/redis`.
207
208
 
208
209
  ```Gemfile
@@ -360,7 +361,7 @@ db.to_xlsx('path/to/file.xlsx')
360
361
  ```ruby
361
362
  r.allbut([:a, :b, ...]) # remove specified attributes
362
363
  r.autowrap(split: '_') # structure a flat relation, split: '_' is the default
363
- r.autosummarize([:a, :b, ...], x: :sum) # (experimental) usual summarizers supported
364
+ r.autosummarize([:a, :b, ...], x: :sum) # usual summarizers supported
364
365
  r.constants(x: 12, ...) # add constant attributes (sometimes useful in unions)
365
366
  r.cross_product(right) # cross product, alias `cross_join`
366
367
  r.extend(x: ->(t){ ... }, ...) # add computed attributes
@@ -536,12 +537,54 @@ probably never be.
536
537
  building an AST internally first. Strictly speaking this makes Bmg less
537
538
  powerful than Alf since optimizations cannot be turned off for now.
538
539
 
540
+ ## Stability and versioning
541
+
542
+ As of 1.0, Bmg follows [semantic versioning](https://semver.org). Breaking
543
+ changes to the API described below only happen on a major version.
544
+
545
+ The following is public, and covered by that promise:
546
+
547
+ * the factory methods on `Bmg` and `Bmg::Relation` (`in_memory`, `sequel`,
548
+ `csv`, `json`, `yaml`, `excel`, `text_file`, `generate`, `mutable`, ...);
549
+ * the relational operators listed under [Supported
550
+ operators](#supported-operators), their arguments and their documented
551
+ options;
552
+ * the predicates and summarizers listed under [Supported
553
+ predicates](#supported-predicates) and [Supported
554
+ summaries](#supported-summaries);
555
+ * `Bmg::Relation` as an interface to include, with `each` yielding symbolized
556
+ tuples, as shown in [Your own relations](#your-own-relations);
557
+ * `Bmg::Type`, `Bmg::Database`, and the reader/writer output preferences.
558
+
559
+ The following is explicitly *not* covered, and may change in a minor version:
560
+
561
+ * the underscored optimization hooks (`_restrict`, `_project`, ...). They are
562
+ documented in [Your own relations](#your-own-relations) because adapters
563
+ need them, but the protocol evolves as new optimizations are added. If you
564
+ override them, pin a minor version and read the CHANGELOG.
565
+ * the shape of the ASTs returned by `to_ast`, and the SQL actually generated
566
+ for a given query tree. Both are observable, and both are expected to
567
+ change as the compiler improves; only the *semantics* of a relation is
568
+ guaranteed.
569
+ * anything under `Bmg::Sql`, `Bmg::Sequel::Translator` and the sexpr grammar,
570
+ which are compiler internals.
571
+
539
572
  ## Contribute
540
573
 
541
574
  Please use github issues and pull requests for all questions, bug reports,
542
575
  and contributions. Don't hesitate to get in touch with us with an early code
543
576
  spike if you plan to add non trivial features.
544
577
 
578
+ The `bench/` folder holds benchmarks for the in-memory operators and for the
579
+ SQL compiler. Run one with `bundle exec ruby bench/operators.rb`. To compare
580
+ two versions of the code, point `BMG_LIB` at another checkout and alternate
581
+ between the two — benchmarking both in one batch, then the other, mostly
582
+ measures how busy your machine was:
583
+
584
+ ```
585
+ BMG_LIB=/path/to/other/lib bundle exec ruby bench/operators.rb
586
+ ```
587
+
545
588
  ## Licence
546
589
 
547
590
  This software is distributed by Enspirit SRL under a MIT Licence. Please
@@ -103,7 +103,7 @@ module Bmg
103
103
  private
104
104
 
105
105
  def tuple_allbut(tuple)
106
- TupleAlgebra.allbut(tuple, @butlist)
106
+ TupleAlgebra.allbut(tuple, @butlist_index ||= TupleAlgebra.membership(@butlist))
107
107
  end
108
108
 
109
109
  def valid_tuple!(tuple)
@@ -5,9 +5,6 @@ module Bmg
5
5
  #
6
6
  # Autosummarize helps structuring the results of a big flat join.
7
7
  #
8
- # This operator is still largely experimental and should be used with
9
- # care...
10
- #
11
8
  class Autosummarize
12
9
  include Operator::Unary
13
10
 
@@ -294,10 +291,11 @@ module Bmg
294
291
  v = v.reject{|tuple| tuple[@x].nil? }
295
292
  v = v.sort(&@sorter) if @sorter
296
293
  v.each do |tuple|
297
- h[tuple[@x]] ||= []
298
- h[tuple[@x]] << tuple[@y]
299
- h[tuple[@x]].uniq!
294
+ (h[tuple[@x]] ||= []) << tuple[@y]
300
295
  end
296
+ # deduplicating once at the end, instead of on every append,
297
+ # keeps this linear in the number of observed `y`s
298
+ h.each_value(&:uniq!)
301
299
  h
302
300
  end
303
301
 
@@ -34,6 +34,7 @@ module Bmg
34
34
  def each(&bl)
35
35
  return to_enum unless block_given?
36
36
  index = Hash.new{|h,k| h[k] = k.merge(as => empty_group) }
37
+ attrs = attrs_index
37
38
  operand.each do |tuple|
38
39
  key = TupleAlgebra.allbut(tuple, attrs)
39
40
  sub = TupleAlgebra.project(tuple, attrs)
@@ -76,6 +77,10 @@ module Bmg
76
77
 
77
78
  private
78
79
 
80
+ def attrs_index
81
+ @attrs_index ||= TupleAlgebra.membership(attrs)
82
+ end
83
+
79
84
  def empty_group
80
85
  Relation::InMemory.new(group_type, Set.new)
81
86
  end
@@ -34,6 +34,8 @@ module Bmg
34
34
 
35
35
  }
36
36
 
37
+ EMPTY_BUTLIST = [].freeze
38
+
37
39
  def initialize(type, left, right, as, on, options = {})
38
40
  @type = type
39
41
  @left = left
@@ -91,8 +93,9 @@ module Bmg
91
93
  index = build_right_index(right_rel)
92
94
 
93
95
  # each left with image from right index
96
+ on = on_index
94
97
  left_rel.each do |tuple|
95
- key = tuple_project(tuple, on)
98
+ key = TupleAlgebra.project(tuple, on)
96
99
  image = index[key] || (options[:array] ? [] : empty_image)
97
100
  yield tuple.merge(as => image)
98
101
  end
@@ -100,10 +103,11 @@ module Bmg
100
103
 
101
104
  def build_right_index(right)
102
105
  index = Hash.new{|h,k| h[k] = empty_image }
103
- butlist = options[:preserve] ? [] : on
106
+ on = on_index
107
+ butlist = options[:preserve] ? EMPTY_BUTLIST : on
104
108
  right.each_with_object(index) do |t, index|
105
- key = tuple_project(t, on)
106
- index[key].operand << tuple_allbut(t, butlist)
109
+ key = TupleAlgebra.project(t, on)
110
+ index[key].operand << TupleAlgebra.allbut(t, butlist)
107
111
  end
108
112
  if opt = options[:array]
109
113
  sorter = to_sorter(opt)
@@ -252,12 +256,8 @@ module Bmg
252
256
 
253
257
  private
254
258
 
255
- def tuple_project(tuple, on)
256
- TupleAlgebra.project(tuple, on)
257
- end
258
-
259
- def tuple_allbut(tuple, butlist)
260
- TupleAlgebra.allbut(tuple, butlist)
259
+ def on_index
260
+ @on_index ||= TupleAlgebra.membership(on)
261
261
  end
262
262
 
263
263
  def image_type
@@ -27,13 +27,14 @@ module Bmg
27
27
  def each
28
28
  return to_enum unless block_given?
29
29
  index = Hash.new
30
+ on = on_index
30
31
  right.each_with_object(index) do |t, index|
31
- key = tuple_project(t, on)
32
+ key = TupleAlgebra.project(t, on)
32
33
  index[key] ||= []
33
34
  index[key] << t
34
35
  end
35
36
  left.each do |tuple|
36
- key = tuple_project(tuple, on)
37
+ key = TupleAlgebra.project(tuple, on)
37
38
  if to_join = index[key]
38
39
  to_join.each do |right|
39
40
  yield right.merge(tuple)
@@ -113,8 +114,8 @@ module Bmg
113
114
 
114
115
  private
115
116
 
116
- def tuple_project(tuple, on)
117
- TupleAlgebra.project(tuple, on)
117
+ def on_index
118
+ @on_index ||= TupleAlgebra.membership(on)
118
119
  end
119
120
 
120
121
  end # class Join
@@ -25,12 +25,13 @@ module Bmg
25
25
  def each
26
26
  return to_enum unless block_given?
27
27
  index = Hash.new
28
+ on = on_index
28
29
  right.each_with_object(index) do |t, index|
29
- key = tuple_project(t, on)
30
+ key = TupleAlgebra.project(t, on)
30
31
  index[key] = true
31
32
  end
32
33
  left.each do |tuple|
33
- key = tuple_project(tuple, on)
34
+ key = TupleAlgebra.project(tuple, on)
34
35
  yield tuple if index.has_key?(key)
35
36
  end
36
37
  end
@@ -73,8 +74,8 @@ module Bmg
73
74
 
74
75
  private
75
76
 
76
- def tuple_project(tuple, on)
77
- TupleAlgebra.project(tuple, on)
77
+ def on_index
78
+ @on_index ||= TupleAlgebra.membership(on)
78
79
  end
79
80
 
80
81
  end # class Matching
@@ -25,12 +25,13 @@ module Bmg
25
25
  def each
26
26
  return to_enum unless block_given?
27
27
  index = Hash.new
28
+ on = on_index
28
29
  right.each_with_object(index) do |t, index|
29
- key = tuple_project(t, on)
30
+ key = TupleAlgebra.project(t, on)
30
31
  index[key] = true
31
32
  end
32
33
  left.each do |tuple|
33
- key = tuple_project(tuple, on)
34
+ key = TupleAlgebra.project(tuple, on)
34
35
  yield tuple unless index.has_key?(key)
35
36
  end
36
37
  end
@@ -57,8 +58,8 @@ module Bmg
57
58
 
58
59
  private
59
60
 
60
- def tuple_project(tuple, on)
61
- TupleAlgebra.project(tuple, on)
61
+ def on_index
62
+ @on_index ||= TupleAlgebra.membership(on)
62
63
  end
63
64
 
64
65
  end # class NotMatching
@@ -80,7 +80,8 @@ module Bmg
80
80
  private
81
81
 
82
82
  def tuple_project(tuple)
83
- tuple.dup.delete_if{|k,_| !@attrlist.include?(k) }
83
+ attrs = (@attrs_index ||= TupleAlgebra.membership(@attrlist))
84
+ tuple.dup.delete_if{|k,_| !attrs.include?(k) }
84
85
  end
85
86
 
86
87
  def valid_tuple!(tuple)
@@ -51,8 +51,9 @@ module Bmg
51
51
  }]
52
52
  }
53
53
  # iterate each tuple
54
+ by_index = TupleAlgebra.membership(@by)
54
55
  @operand.each do |tuple|
55
- key = TupleAlgebra.project(tuple, @by)
56
+ key = TupleAlgebra.project(tuple, by_index)
56
57
  # apply them all and create a new memo
57
58
  result[key] = Hash[@summarization.map{|k,v|
58
59
  [ k, v.happens(result[key][k], tuple) ]
@@ -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
@@ -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)
@@ -1,6 +1,24 @@
1
1
  module Bmg
2
+ #
3
+ # Tuple-level counterparts of the relational operators.
4
+ #
2
5
  module TupleAlgebra
3
6
 
7
+ # `allbut` and `project` below test every attribute of every tuple for
8
+ # membership in an attribute list, so the cost of that lookup is paid
9
+ # `tuples x attributes` times. Converting the list to a Set is worth it,
10
+ # but only past a few elements: below that an Array scan wins, on every
11
+ # ruby (measured in bench/set_vs_array.rb). Join keys, in particular,
12
+ # are usually one or two attributes and are better left alone.
13
+ #
14
+ # Callers that iterate are expected to hoist this out of their loop.
15
+ SET_FROM = 3
16
+
17
+ def membership(attrlist)
18
+ attrlist.size < SET_FROM ? attrlist : attrlist.to_set
19
+ end
20
+ module_function :membership
21
+
4
22
  def allbut(tuple, butlist)
5
23
  tuple.reject{|k,v| butlist.include?(k) }
6
24
  end
data/lib/bmg/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Bmg
2
2
  module Version
3
- MAJOR = 0
4
- MINOR = 23
5
- TINY = 6
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
data/lib/bmg.rb CHANGED
@@ -36,7 +36,4 @@ module Bmg
36
36
 
37
37
  # Add all factory methods
38
38
  extend Factory
39
-
40
- # Deprecated
41
- Leaf = Relation::InMemory
42
39
  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: 1.0.0
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: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: predicate
@@ -37,6 +36,9 @@ dependencies:
37
36
  - - ">="
38
37
  - !ruby/object:Gem::Version
39
38
  version: '2.0'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '3.0'
40
42
  type: :runtime
41
43
  prerelease: false
42
44
  version_requirements: !ruby/object:Gem::Requirement
@@ -44,6 +46,9 @@ dependencies:
44
46
  - - ">="
45
47
  - !ruby/object:Gem::Version
46
48
  version: '2.0'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '3.0'
47
52
  - !ruby/object:Gem::Dependency
48
53
  name: rake
49
54
  requirement: !ruby/object:Gem::Requirement
@@ -79,6 +84,9 @@ dependencies:
79
84
  - - ">="
80
85
  - !ruby/object:Gem::Version
81
86
  version: '2.8'
87
+ - - "<"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.0'
82
90
  type: :development
83
91
  prerelease: false
84
92
  version_requirements: !ruby/object:Gem::Requirement
@@ -86,6 +94,9 @@ dependencies:
86
94
  - - ">="
87
95
  - !ruby/object:Gem::Version
88
96
  version: '2.8'
97
+ - - "<"
98
+ - !ruby/object:Gem::Version
99
+ version: '4.0'
89
100
  - !ruby/object:Gem::Dependency
90
101
  name: write_xlsx
91
102
  requirement: !ruby/object:Gem::Requirement
@@ -104,44 +115,90 @@ dependencies:
104
115
  name: sequel
105
116
  requirement: !ruby/object:Gem::Requirement
106
117
  requirements:
107
- - - ">="
118
+ - - "~>"
108
119
  - !ruby/object:Gem::Version
109
- version: '0'
120
+ version: '5.0'
110
121
  type: :development
111
122
  prerelease: false
112
123
  version_requirements: !ruby/object:Gem::Requirement
113
124
  requirements:
114
- - - ">="
125
+ - - "~>"
115
126
  - !ruby/object:Gem::Version
116
- version: '0'
127
+ version: '5.0'
117
128
  - !ruby/object:Gem::Dependency
118
129
  name: sqlite3
119
130
  requirement: !ruby/object:Gem::Requirement
120
131
  requirements:
121
132
  - - ">="
122
133
  - !ruby/object:Gem::Version
123
- version: '0'
134
+ version: '1.4'
135
+ - - "<"
136
+ - !ruby/object:Gem::Version
137
+ version: '3.0'
124
138
  type: :development
125
139
  prerelease: false
126
140
  version_requirements: !ruby/object:Gem::Requirement
127
141
  requirements:
128
142
  - - ">="
129
143
  - !ruby/object:Gem::Version
130
- version: '0'
144
+ version: '1.4'
145
+ - - "<"
146
+ - !ruby/object:Gem::Version
147
+ version: '3.0'
131
148
  - !ruby/object:Gem::Dependency
132
149
  name: activesupport
133
150
  requirement: !ruby/object:Gem::Requirement
134
151
  requirements:
135
152
  - - ">="
136
153
  - !ruby/object:Gem::Version
137
- version: '0'
154
+ version: '6.0'
155
+ - - "<"
156
+ - !ruby/object:Gem::Version
157
+ version: '9.0'
138
158
  type: :development
139
159
  prerelease: false
140
160
  version_requirements: !ruby/object:Gem::Requirement
141
161
  requirements:
142
162
  - - ">="
143
163
  - !ruby/object:Gem::Version
144
- version: '0'
164
+ version: '6.0'
165
+ - - "<"
166
+ - !ruby/object:Gem::Version
167
+ version: '9.0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: ostruct
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0.2'
175
+ - - "<"
176
+ - !ruby/object:Gem::Version
177
+ version: '1.0'
178
+ type: :development
179
+ prerelease: false
180
+ version_requirements: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0.2'
185
+ - - "<"
186
+ - !ruby/object:Gem::Version
187
+ version: '1.0'
188
+ - !ruby/object:Gem::Dependency
189
+ name: benchmark-ips
190
+ requirement: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '2.14'
195
+ type: :development
196
+ prerelease: false
197
+ version_requirements: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '2.14'
145
202
  description: Bmg is Alf's relational algebra for ruby, but much simpler and lighter
146
203
  than Alf itself
147
204
  email: blambeau@gmail.com
@@ -233,6 +290,7 @@ files:
233
290
  - lib/bmg/sql/ext/predicate/tautology.rb
234
291
  - lib/bmg/sql/grammar.rb
235
292
  - lib/bmg/sql/grammar.sexp.yml
293
+ - lib/bmg/sql/nodes/case_when.rb
236
294
  - lib/bmg/sql/nodes/column_name.rb
237
295
  - lib/bmg/sql/nodes/cross_join.rb
238
296
  - lib/bmg/sql/nodes/except.rb
@@ -328,11 +386,14 @@ files:
328
386
  - lib/bmg/xlsx.rb
329
387
  - tasks/gem.rake
330
388
  - tasks/test.rake
331
- homepage: http://github.com/enspirit/bmg
389
+ homepage: https://github.com/enspirit/bmg
332
390
  licenses:
333
391
  - MIT
334
- metadata: {}
335
- post_install_message:
392
+ metadata:
393
+ source_code_uri: https://github.com/enspirit/bmg
394
+ changelog_uri: https://github.com/enspirit/bmg/blob/master/CHANGELOG.md
395
+ bug_tracker_uri: https://github.com/enspirit/bmg/issues
396
+ documentation_uri: https://www.relational-algebra.dev/
336
397
  rdoc_options: []
337
398
  require_paths:
338
399
  - lib
@@ -340,15 +401,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
340
401
  requirements:
341
402
  - - ">="
342
403
  - !ruby/object:Gem::Version
343
- version: '0'
404
+ version: '2.7'
344
405
  required_rubygems_version: !ruby/object:Gem::Requirement
345
406
  requirements:
346
407
  - - ">="
347
408
  - !ruby/object:Gem::Version
348
409
  version: '0'
349
410
  requirements: []
350
- rubygems_version: 3.4.19
351
- signing_key:
411
+ rubygems_version: 3.6.9
352
412
  specification_version: 4
353
413
  summary: Bmg is Alf's successor.
354
414
  test_files: []