bmg 0.19.1 → 0.20.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
  SHA1:
3
- metadata.gz: 52f3bf703312e48ca61bb2dbc88711be1dde8a89
4
- data.tar.gz: 4f5029df9dc203d7ec82c0da066771ed61fe6182
3
+ metadata.gz: 35789fefdd2032b4e807b730fb2fc6f5abe163e1
4
+ data.tar.gz: 2f20b907d1f66560cbe802c6b3fe3e4064b7bfd0
5
5
  SHA512:
6
- metadata.gz: e9ac87e62f17250706eefb5ce091e3e77f0b91268910ae0a9fa1446eda6adaeed4ef852d022ecab8c9869b5b5da34db678bf7af811c93de08aad1bec28a39b37
7
- data.tar.gz: 7c4117b376c5a406612729c9dfb27b5e22e76ec96e269652bf3a99b53d5c890dfc38c3d269558d08267ca8d4aeaad1c60bad89bc4d24e3b0079a10fc49bff73d
6
+ metadata.gz: 8c1182190f70a94825505f358701e3ba728eadcfe7026da40f10b7b7dc9b2ff21d8f2f9ec2ccf24baa880b81728e9ef530830e168a33a2742a196c135445d0e5
7
+ data.tar.gz: b2e7ef1f3bf5983cfd3ff6fbaff820a04f9f118e39df88728ad6c87fec7261269b9fe22f60d44f9d141ce47c78db5e91399345475835f5c88c7b44b16a1c5873
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # Bmg, a relational algebra (Alf's successor)!
2
2
 
3
- [![Build Status](https://travis-ci.com/enspirit/bmg.svg?branch=master)](https://travis-ci.com/enspirit/bmg)
4
-
5
3
  Bmg is a relational algebra implemented as a ruby library. It implements the
6
4
  [Relation as First-Class Citizen](http://www.try-alf.org/blog/2013-10-21-relations-as-first-class-citizen)
7
5
  paradigm contributed with [Alf](http://www.try-alf.org/) a few years ago.
@@ -18,6 +16,7 @@ further down this README.
18
16
  * [Memory relations](#memory-relations)
19
17
  * [Connecting to SQL databases](#connecting-to-sql-databases)
20
18
  * [Reading files (csv, excel, text)](#reading-files-csv-excel-text)
19
+ * [Connecting to Redis databases](#connecting-to-redis-databases)
21
20
  * [Your own relations](#your-own-relations)
22
21
  * [List of supported operators](#supported-operators)
23
22
  * [How is this different?](#how-is-this-different)
@@ -174,6 +173,43 @@ r.type.attrlist
174
173
  In this scenario, non matching lines are skipped. The `:line` attribute keeps
175
174
  being used to have at least one candidate key (so to speak).
176
175
 
176
+ ### Connecting to Redis databases
177
+
178
+ Bmg currently requires `bmg-redis` and `redis >= 4.6` to connect
179
+ to Redis databases. You also need to require `bmg/redis`.
180
+
181
+ ```Gemfile
182
+ gem 'bmg'
183
+ gem 'bmg-redis'
184
+ ```
185
+
186
+ ```ruby
187
+ require 'redis' # also done by 'bmg/redis' below
188
+ require 'bmg'
189
+ require 'bmg/redis'
190
+ ```
191
+
192
+ Then, you can create Redis relation variables (aka relvars) like
193
+ this:
194
+
195
+ ```ruby
196
+ type = Bmg::Type::ANY.with_keys([[:id]])
197
+ r = Bmg.redis(type, {
198
+ key_prefix: "suppliers",
199
+ redis: Redis.new,
200
+ serializer: :marshal
201
+ })
202
+ ```
203
+
204
+ The key prefix will be used to distinguish the tuples from other
205
+ elements in the same database (e.g. tuples from other relvars).
206
+ The serializer is either `:marshal` or `:json`. Please note that
207
+ types are not preserved when using the second one (all attribute
208
+ values will come back as strings, but keys will be symbolized).
209
+
210
+ The redis relvars support basic algorithms for insert/update/delete.
211
+ No optimization is currently supported.
212
+
177
213
  ### Your own relations
178
214
 
179
215
  As noted earlier, Bmg has a simple relation interface where you only have to
@@ -210,6 +246,7 @@ r.autowrap(split: '_') # structure a flat relation, split:
210
246
  r.autosummarize([:a, :b, ...], x: :sum) # (experimental) usual summarizers supported
211
247
  r.constants(x: 12, ...) # add constant attributes (sometimes useful in unions)
212
248
  r.extend(x: ->(t){ ... }, ...) # add computed attributes
249
+ r.extend(x: :y) # shortcut for r.extend(x: ->(t){ t[:y] })
213
250
  r.exclude(predicate) # shortcut for restrict(!predicate)
214
251
  r.group([:a, :b, ...], :x) # relation-valued attribute from attributes
215
252
  r.image(right, :x, [:a, :b, ...]) # relation-valued attribute from another relation
@@ -50,12 +50,12 @@ module Bmg
50
50
  end
51
51
  end
52
52
 
53
- def update(tuple)
54
- operand.update(valid_tuple!(tuple))
53
+ def update(tuple, predicate = Predicate.tautology)
54
+ operand.update(valid_tuple!(tuple), predicate)
55
55
  end
56
56
 
57
- def delete
58
- operand.delete
57
+ def delete(predicate = Predicate.tautology)
58
+ operand.delete(predicate)
59
59
  end
60
60
 
61
61
  def to_ast
@@ -10,15 +10,15 @@ module Bmg
10
10
  class Constants
11
11
  include Operator::Unary
12
12
 
13
- def initialize(type, operand, constants)
13
+ def initialize(type, operand, the_constants)
14
14
  @type = type
15
15
  @operand = operand
16
- @constants = constants
16
+ @the_constants = the_constants
17
17
  end
18
18
 
19
19
  protected
20
20
 
21
- attr_reader :constants
21
+ attr_reader :the_constants
22
22
 
23
23
  public
24
24
 
@@ -32,27 +32,27 @@ module Bmg
32
32
  def insert(arg)
33
33
  case arg
34
34
  when Hash then operand.insert(allbut_constants(arg))
35
- when Relation then operand.insert(arg.allbut(constants.keys))
35
+ when Relation then operand.insert(arg.allbut(the_constants.keys))
36
36
  when Enumerable then operand.insert(arg.map{|t| allbut_constants(t) })
37
37
  else
38
38
  super
39
39
  end
40
40
  end
41
41
 
42
- def update(tuple)
43
- shared = tuple.keys & constants.keys
42
+ def update(tuple, predicate = Predicate.tautology)
43
+ shared = tuple.keys & the_constants.keys
44
44
  on_tuple = TupleAlgebra.project(tuple, shared)
45
- on_const = TupleAlgebra.project(constants, shared)
45
+ on_const = TupleAlgebra.project(the_constants, shared)
46
46
  raise InvalidUpdateError, "Cannot violate relvar predicate" unless on_tuple == on_const
47
- operand.update(allbut_constants(tuple))
47
+ operand.update(allbut_constants(tuple), predicate)
48
48
  end
49
49
 
50
- def delete
51
- operand.delete
50
+ def delete(predicate = Predicate.tautology)
51
+ operand.delete(predicate)
52
52
  end
53
53
 
54
54
  def to_ast
55
- [ :constants, operand.to_ast, constants.dup ]
55
+ [ :constants, operand.to_ast, the_constants.dup ]
56
56
  end
57
57
 
58
58
  public ### for internal reasons
@@ -65,32 +65,32 @@ module Bmg
65
65
 
66
66
  def _page(type, ordering, page_index, options)
67
67
  attrs = ordering.map{|(k,v)| k}
68
- cs_attrs = constants.keys
68
+ cs_attrs = the_constants.keys
69
69
  if (attrs & cs_attrs).empty?
70
70
  operand
71
71
  .page(ordering, page_index, options)
72
- .constants(constants)
72
+ .constants(the_constants)
73
73
  else
74
74
  super
75
75
  end
76
76
  end
77
77
 
78
78
  def _restrict(type, predicate)
79
- # bottom_p makes no reference to constants, top_p possibly
79
+ # bottom_p makes no reference to the_constants, top_p possibly
80
80
  # does...
81
- top_p, bottom_p = predicate.and_split(constants.keys)
81
+ top_p, bottom_p = predicate.and_split(the_constants.keys)
82
82
  if top_p.tautology?
83
- # push all situation: predicate made no reference to constants
83
+ # push all situation: predicate made no reference to the_constants
84
84
  result = operand
85
85
  result = result.restrict(bottom_p)
86
- result = result.constants(constants)
86
+ result = result.constants(the_constants)
87
87
  result
88
- elsif (top_p.free_variables - constants.keys).empty?
89
- # top_p applies to constants only
90
- if eval = top_p.evaluate(constants)
88
+ elsif (top_p.free_variables - the_constants.keys).empty?
89
+ # top_p applies to the_constants only
90
+ if eval = top_p.evaluate(the_constants)
91
91
  result = operand
92
92
  result = result.restrict(bottom_p)
93
- result = result.constants(constants)
93
+ result = result.constants(the_constants)
94
94
  result
95
95
  else
96
96
  Relation.empty(type)
@@ -104,7 +104,7 @@ module Bmg
104
104
  # of them
105
105
  result = operand
106
106
  result = result.restrict(bottom_p)
107
- result = result.constants(constants)
107
+ result = result.constants(the_constants)
108
108
  result = result.restrict(top_p)
109
109
  result
110
110
  end
@@ -115,17 +115,17 @@ module Bmg
115
115
  protected ### inspect
116
116
 
117
117
  def args
118
- [ constants ]
118
+ [ the_constants ]
119
119
  end
120
120
 
121
121
  private
122
122
 
123
123
  def extend_it(tuple)
124
- tuple.merge(@constants)
124
+ tuple.merge(@the_constants)
125
125
  end
126
126
 
127
127
  def allbut_constants(tuple)
128
- TupleAlgebra.allbut(tuple, constants.keys)
128
+ TupleAlgebra.allbut(tuple, the_constants.keys)
129
129
  end
130
130
 
131
131
  end # class Constants
@@ -42,12 +42,12 @@ module Bmg
42
42
  end
43
43
  end
44
44
 
45
- def update(tuple)
46
- operand.update(allbut_extkeys(tuple))
45
+ def update(tuple, predicate = Predicate.tautology)
46
+ operand.update(allbut_extkeys(tuple), predicate)
47
47
  end
48
48
 
49
- def delete
50
- operand.delete
49
+ def delete(predicate = Predicate.tautology)
50
+ operand.delete(predicate)
51
51
  end
52
52
 
53
53
  def to_ast
@@ -160,7 +160,12 @@ module Bmg
160
160
 
161
161
  def extend_it(tuple)
162
162
  @extension.each_with_object(tuple.dup) { |(k,v), memo|
163
- memo[k] = v.call(tuple)
163
+ memo[k] = case v
164
+ when Symbol
165
+ tuple[v]
166
+ else
167
+ v.call(tuple)
168
+ end
164
169
  memo
165
170
  }
166
171
  end
@@ -49,12 +49,12 @@ module Bmg
49
49
  end
50
50
  end
51
51
 
52
- def update(tuple)
53
- operand.update(valid_tuple!(tuple))
52
+ def update(tuple, predicate = Predicate.tautology)
53
+ operand.update(valid_tuple!(tuple), predicate)
54
54
  end
55
55
 
56
- def delete
57
- operand.delete
56
+ def delete(predicate = Predicate.tautology)
57
+ operand.delete(predicate)
58
58
  end
59
59
 
60
60
  def to_ast
@@ -45,16 +45,17 @@ module Bmg
45
45
  end
46
46
  end
47
47
 
48
- def update(arg)
48
+ def update(arg, predicate = Predicate.tautology)
49
49
  case arg
50
- when Hash then operand.update(rename_tuple(arg, reverse_renaming))
50
+ when Hash
51
+ operand.update(rename_tuple(arg, reverse_renaming), predicate)
51
52
  else
52
53
  super
53
54
  end
54
55
  end
55
56
 
56
- def delete
57
- operand.delete
57
+ def delete(predicate = Predicate.tautology)
58
+ operand.delete(predicate)
58
59
  end
59
60
 
60
61
  def to_ast
@@ -32,6 +32,18 @@ module Bmg
32
32
  end
33
33
  end
34
34
 
35
+ def insert(tuple)
36
+ operand.insert(tuple)
37
+ end
38
+
39
+ def update(updating, predicate = Predicate.tautology)
40
+ operand.update(updating, predicate & self.predicate)
41
+ end
42
+
43
+ def delete(predicate = Predicate.tautology)
44
+ operand.delete(predicate & self.predicate)
45
+ end
46
+
35
47
  def to_ast
36
48
  [ :restrict, operand.to_ast, predicate.sexpr ]
37
49
  end
@@ -0,0 +1,23 @@
1
+ module Bmg
2
+ module Relation
3
+ class InMemory
4
+ class Mutable < InMemory
5
+ def insert(arg)
6
+ raise ArgumentError unless arg.is_a?(Hash)
7
+
8
+ @operand << arg.dup
9
+ end
10
+
11
+ def update(updating, predicate = Predicate.tautology)
12
+ @operand = @operand.map{|t|
13
+ predicate.call(t) ? t.merge(updating) : t
14
+ }
15
+ end
16
+
17
+ def delete(predicate = Predicate.tautology)
18
+ @operand = @operand.select{|t| predicate.call(t) }
19
+ end
20
+ end # class Mutable
21
+ end # class InMemory
22
+ end # module Relation
23
+ end # module Bmg
@@ -41,3 +41,4 @@ module Bmg
41
41
  end # class InMemory
42
42
  end # module Relation
43
43
  end # module Bmg
44
+ require_relative 'in_memory/mutable'
data/lib/bmg/relation.rb CHANGED
@@ -74,16 +74,16 @@ module Bmg
74
74
  one_or_yield{ nil }
75
75
  end
76
76
 
77
- def insert(arg)
78
- raise InvalidUpdateError, "Cannot insert into this Relvar"
77
+ def insert(*args, &bl)
78
+ raise InvalidUpdateError, "Cannot insert into #{self.class.name}"
79
79
  end
80
80
 
81
- def update(arg)
82
- raise InvalidUpdateError, "Cannot update this Relvar"
81
+ def update(*args, &bl)
82
+ raise InvalidUpdateError, "Cannot update #{self.class.name}"
83
83
  end
84
84
 
85
- def delete
86
- raise InvalidUpdateError, "Cannot delete from this Relvar"
85
+ def delete(*args, &bl)
86
+ raise InvalidUpdateError, "Cannot delete from #{self.class.name}"
87
87
  end
88
88
 
89
89
  def visit(&visitor)
@@ -0,0 +1,22 @@
1
+ module Bmg
2
+ module Sequel
3
+ class PredicateTranslator < Sexpr::Processor
4
+ include ::Predicate::ToSequel::Methods
5
+
6
+ def initialize(parent)
7
+ @parent = parent
8
+ end
9
+
10
+ public ### Predicate hack
11
+
12
+ def on_opaque(sexpr)
13
+ @parent.apply(sexpr.last)
14
+ end
15
+
16
+ def on_exists(sexpr)
17
+ @parent.apply(sexpr.last).exists
18
+ end
19
+
20
+ end # class PredicateTranslator
21
+ end # module Sequel
22
+ end # module Bmg
@@ -13,8 +13,13 @@ module Bmg
13
13
  dataset.each(&bl)
14
14
  end
15
15
 
16
- def delete
17
- base_table.delete
16
+ def delete(predicate = Predicate.tautology)
17
+ target = base_table
18
+ unless predicate.tautology?
19
+ compiled = compile_predicate(predicate)
20
+ target = base_table.where(compiled)
21
+ end
22
+ target.delete
18
23
  end
19
24
 
20
25
  def insert(arg)
@@ -30,8 +35,13 @@ module Bmg
30
35
  end
31
36
  end
32
37
 
33
- def update(arg)
34
- base_table.update(arg)
38
+ def update(arg, predicate = Predicate.tautology)
39
+ target = base_table
40
+ unless predicate.tautology?
41
+ compiled = compile_predicate(predicate)
42
+ target = base_table.where(compiled)
43
+ end
44
+ target.update(arg)
35
45
  end
36
46
 
37
47
  def _count
@@ -73,6 +83,10 @@ module Bmg
73
83
  operand.expr
74
84
  end
75
85
 
86
+ def compile_predicate(predicate)
87
+ Translator.new(sequel_db).compile_predicate(predicate)
88
+ end
89
+
76
90
  end # class Relation
77
91
  end # module Sequel
78
92
  end # module Bmg
@@ -81,6 +81,7 @@ module Bmg
81
81
  case sexpr.func_name
82
82
  when :cast
83
83
  to_cast = apply(sexpr.func_args.first)
84
+ to_cast = ::Sequel.expr(nil) if to_cast.nil?
84
85
  type = sexpr.func_args.last.last
85
86
  to_cast.cast(type)
86
87
  else
@@ -178,36 +179,19 @@ module Bmg
178
179
  sexpr.last
179
180
  end
180
181
 
181
- private
182
-
183
- def dataset(expr)
184
- return expr if ::Sequel::Dataset===expr
185
- sequel_db[expr]
186
- end
182
+ public
187
183
 
188
184
  def compile_predicate(predicate)
189
185
  PredicateTranslator.new(self).call(predicate)
190
186
  end
191
187
 
192
- class PredicateTranslator < Sexpr::Processor
193
- include ::Predicate::ToSequel::Methods
194
-
195
- def initialize(parent)
196
- @parent = parent
197
- end
198
-
199
- public ### Predicate hack
200
-
201
- def on_opaque(sexpr)
202
- @parent.apply(sexpr.last)
203
- end
188
+ private
204
189
 
205
- def on_exists(sexpr)
206
- @parent.apply(sexpr.last).exists
190
+ def dataset(expr)
191
+ return expr if ::Sequel::Dataset===expr
192
+ sequel_db[expr]
207
193
  end
208
194
 
209
- end
210
-
211
195
  end # class Translator
212
196
  end # module Sequel
213
197
  end # module Bmg
data/lib/bmg/sequel.rb CHANGED
@@ -62,5 +62,6 @@ module Bmg
62
62
  end
63
63
  require_relative 'sequel/ext'
64
64
  require_relative 'sequel/translator'
65
+ require_relative 'sequel/predicate_translator'
65
66
  require_relative 'sequel/type_inference'
66
67
  require_relative 'sequel/relation'
@@ -0,0 +1,33 @@
1
+ module Bmg
2
+ module Sql
3
+ class Processor
4
+ class Extend < Processor
5
+
6
+ def initialize(extension, builder)
7
+ super(builder)
8
+ @extension = extension
9
+ end
10
+ attr_reader :extension
11
+
12
+ def on_set_operator(sexpr)
13
+ apply(builder.from_self(sexpr))
14
+ end
15
+ alias :on_union :on_set_operator
16
+ alias :on_except :on_set_operator
17
+ alias :on_intersect :on_set_operator
18
+
19
+ def on_select_star(sexpr)
20
+ raise NotImplementedError, "Extend on * is not supported"
21
+ end
22
+
23
+ def on_select_list(sexpr)
24
+ sexpr + extension.each_pair.map{|(k,v)|
25
+ desaliased = sexpr.desaliaser[v]
26
+ [:select_item, desaliased, [:column_name, k] ]
27
+ }
28
+ end
29
+
30
+ end # class Extend
31
+ end # class Processor
32
+ end # module Sql
33
+ end # module Bmg
@@ -10,7 +10,7 @@ module Bmg
10
10
 
11
11
  def on_select_list(sexpr)
12
12
  reordered = sexpr.sexpr_body.sort{|i1,i2|
13
- @indexes[i1.as_name] <=> @indexes[i2.as_name]
13
+ @indexes[i1.as_name.to_s] <=> @indexes[i2.as_name.to_s]
14
14
  }
15
15
  reordered.unshift(:select_list)
16
16
  end
@@ -39,9 +39,10 @@ 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
+
42
43
  left = Predicate.new(Predicate::Grammar.sexpr(sexpr.predicate)).unqualify
43
44
  right = Predicate.new(Predicate::Grammar.sexpr(@predicate.sexpr)).unqualify
44
- return (left & right).contradiction?
45
+ (left & right).contradiction?
45
46
  end
46
47
 
47
48
  end # class Where
@@ -71,6 +71,7 @@ require_relative 'processor/distinct'
71
71
  require_relative 'processor/all'
72
72
  require_relative 'processor/clip'
73
73
  require_relative 'processor/constants'
74
+ require_relative 'processor/extend'
74
75
  require_relative 'processor/star'
75
76
  require_relative 'processor/rename'
76
77
  require_relative 'processor/order_by'
@@ -27,15 +27,15 @@ module Bmg
27
27
  raise NotImplementedError
28
28
  end
29
29
 
30
- def delete
30
+ def delete(*args, &bl)
31
31
  raise NotImplementedError
32
32
  end
33
33
 
34
- def insert(arg)
34
+ def insert(*args, &bl)
35
35
  raise NotImplementedError
36
36
  end
37
37
 
38
- def update(arg)
38
+ def update(*args, &bl)
39
39
  raise NotImplementedError
40
40
  end
41
41
 
@@ -57,6 +57,23 @@ module Bmg
57
57
  _instance(type, builder, expr)
58
58
  end
59
59
 
60
+ def _extend(type, extension)
61
+ supported, unsupported = {}, {}
62
+ extension.each_pair do |k,v|
63
+ (v.is_a?(Symbol) ? supported : unsupported)[k] = v
64
+ end
65
+ if supported.empty?
66
+ Operator::Extend.new(type, self, extension)
67
+ elsif unsupported.empty?
68
+ expr = Processor::Extend.new(supported, builder).call(self.expr)
69
+ _instance(type, builder, expr)
70
+ else
71
+ expr = Processor::Extend.new(supported, builder).call(self.expr)
72
+ operand = _instance(type.allbut(unsupported.keys), builder, expr)
73
+ Operator::Extend.new(type, operand, unsupported)
74
+ end
75
+ end
76
+
60
77
  def _join(type, right, on)
61
78
  if right_expr = extract_compatible_sexpr(right)
62
79
  right_expr = Processor::Requalify.new(builder).call(right_expr)
data/lib/bmg/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 19
4
+ MINOR = 20
5
5
  TINY = 1
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
data/lib/bmg.rb CHANGED
@@ -4,6 +4,11 @@ require 'forwardable'
4
4
  require 'set'
5
5
  module Bmg
6
6
 
7
+ def mutable(enumerable, type = Type::ANY)
8
+ Relation::InMemory::Mutable.new(type, enumerable).spied(main_spy)
9
+ end
10
+ module_function :mutable
11
+
7
12
  def in_memory(enumerable, type = Type::ANY)
8
13
  Relation::InMemory.new(type, enumerable).spied(main_spy)
9
14
  end
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.19.1
4
+ version: 0.20.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-21 00:00:00.000000000 Z
11
+ date: 2022-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: predicate
@@ -174,11 +174,13 @@ files:
174
174
  - lib/bmg/relation.rb
175
175
  - lib/bmg/relation/empty.rb
176
176
  - lib/bmg/relation/in_memory.rb
177
+ - lib/bmg/relation/in_memory/mutable.rb
177
178
  - lib/bmg/relation/materialized.rb
178
179
  - lib/bmg/relation/proxy.rb
179
180
  - lib/bmg/relation/spied.rb
180
181
  - lib/bmg/sequel.rb
181
182
  - lib/bmg/sequel/ext.rb
183
+ - lib/bmg/sequel/predicate_translator.rb
182
184
  - lib/bmg/sequel/relation.rb
183
185
  - lib/bmg/sequel/translator.rb
184
186
  - lib/bmg/sequel/type_inference.rb
@@ -248,6 +250,7 @@ files:
248
250
  - lib/bmg/sql/processor/clip.rb
249
251
  - lib/bmg/sql/processor/constants.rb
250
252
  - lib/bmg/sql/processor/distinct.rb
253
+ - lib/bmg/sql/processor/extend.rb
251
254
  - lib/bmg/sql/processor/flatten.rb
252
255
  - lib/bmg/sql/processor/from_self.rb
253
256
  - lib/bmg/sql/processor/join.rb