bmg 0.19.1 → 0.20.1

Sign up to get free protection for your applications and to get access to all the features.
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