bmg 0.17.3 → 0.17.8
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 +4 -4
- data/README.md +73 -5
- data/lib/bmg.rb +2 -0
- data/lib/bmg/algebra.rb +10 -0
- data/lib/bmg/algebra/shortcuts.rb +6 -0
- data/lib/bmg/operator.rb +1 -0
- data/lib/bmg/operator/transform.rb +57 -0
- data/lib/bmg/relation.rb +14 -0
- data/lib/bmg/relation/proxy.rb +63 -0
- data/lib/bmg/sequel/translator.rb +24 -11
- data/lib/bmg/sql/nodes/column_name.rb +4 -0
- data/lib/bmg/sql/nodes/func_call.rb +4 -0
- data/lib/bmg/sql/nodes/literal.rb +4 -0
- data/lib/bmg/sql/nodes/qualified_name.rb +4 -0
- data/lib/bmg/sql/nodes/select_exp.rb +4 -0
- data/lib/bmg/sql/nodes/select_item.rb +4 -0
- data/lib/bmg/sql/nodes/select_list.rb +4 -0
- data/lib/bmg/sql/nodes/select_star.rb +4 -0
- data/lib/bmg/sql/nodes/summarizer.rb +4 -0
- data/lib/bmg/sql/processor/where.rb +1 -1
- data/lib/bmg/support.rb +2 -0
- data/lib/bmg/support/keys.rb +4 -0
- data/lib/bmg/support/output_preferences.rb +44 -0
- data/lib/bmg/support/tuple_transformer.rb +64 -0
- data/lib/bmg/type.rb +25 -0
- data/lib/bmg/version.rb +1 -1
- data/lib/bmg/writer.rb +1 -0
- data/lib/bmg/writer/csv.rb +42 -0
- metadata +26 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 600739b827185e0d02252824b7b5616662a378b1c907a632ba695536e76d631e
|
4
|
+
data.tar.gz: f327e31860ef19e0ab8177ebd0e3d0c927c003cb25b8e48af202145cdf1ceece
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cd09c0006211c6db5d220ee2371669bd3e29bf32c71eae9fb9ba2c4698392ed172412bbd71c9eded5df34172ab469aa6d20a4117ef263955ed73e8a128cf856
|
7
|
+
data.tar.gz: 8777307569c78001741a597b3ecd9974aa1ee94780495d86f97af38313dcd185c9516d43b9c1635cae5e57606c7422d3e6b3f4889be0b2adcdfeb9f2c30dee24
|
data/README.md
CHANGED
@@ -14,7 +14,7 @@ also much simpler, and make its easier to implement user-defined relations.
|
|
14
14
|
|
15
15
|
## Example
|
16
16
|
|
17
|
-
```
|
17
|
+
```ruby
|
18
18
|
require 'bmg'
|
19
19
|
require 'json'
|
20
20
|
|
@@ -32,13 +32,14 @@ by_city = suppliers
|
|
32
32
|
.group([:sid, :name, :status], :suppliers_in)
|
33
33
|
|
34
34
|
puts JSON.pretty_generate(by_city)
|
35
|
+
# [{...},...]
|
35
36
|
```
|
36
37
|
|
37
38
|
## Connecting to a SQL database
|
38
39
|
|
39
40
|
Bmg requires `sequel >= 3.0` to connect to SQL databases.
|
40
41
|
|
41
|
-
```
|
42
|
+
```ruby
|
42
43
|
require 'sqlite3'
|
43
44
|
require 'bmg'
|
44
45
|
require 'bmg/sequel'
|
@@ -47,16 +48,67 @@ DB = Sequel.connect("sqlite://suppliers-and-parts.db")
|
|
47
48
|
|
48
49
|
suppliers = Bmg.sequel(:suppliers, DB)
|
49
50
|
|
50
|
-
|
51
|
+
big_suppliers = suppliers
|
51
52
|
.restrict(Predicate.neq(status: 30))
|
52
|
-
.to_sql
|
53
53
|
|
54
|
+
puts big_suppliers.to_sql
|
54
55
|
# SELECT `t1`.`sid`, `t1`.`name`, `t1`.`status`, `t1`.`city` FROM `suppliers` AS 't1' WHERE (`t1`.`status` != 30)
|
56
|
+
|
57
|
+
puts JSON.pretty_generate(big_suppliers)
|
58
|
+
# [{...},...]
|
55
59
|
```
|
56
60
|
|
61
|
+
## How is this different from similar libraries?
|
62
|
+
|
63
|
+
1. The libraries you probably know (Sequel, Arel, SQLAlchemy, Korma, jOOQ,
|
64
|
+
etc.) do not implement a genuine relational algebra: their support for
|
65
|
+
chaining relational operators is limited (yielding errors or wrong SQL
|
66
|
+
queries). Bmg **always** allows chaining operators. If it does not, it's
|
67
|
+
a bug. In other words, the following query is 100% valid:
|
68
|
+
|
69
|
+
relation
|
70
|
+
.restrict(...) # aka where
|
71
|
+
.union(...)
|
72
|
+
.summarize(...) # aka group by
|
73
|
+
.restrict(...)
|
74
|
+
|
75
|
+
2. Bmg supports in memory relations, json relations, csv relations, SQL
|
76
|
+
relations and so on. It's not tight to SQL generation, and supports
|
77
|
+
queries accross multiple data sources.
|
78
|
+
|
79
|
+
3. Bmg makes a best effort to optimize queries, simplifying both generated
|
80
|
+
SQL code (low-level accesses to datasources) and in-memory operations.
|
81
|
+
|
82
|
+
4. Bmg supports various *structuring* operators (group, image, autowrap,
|
83
|
+
autosummarize, etc.) and allows building 'non flat' relations.
|
84
|
+
|
85
|
+
## How is this different from Alf?
|
86
|
+
|
87
|
+
1. Bmg's implementation is much simpler than Alf, and uses no ruby core
|
88
|
+
extention.
|
89
|
+
|
90
|
+
2. We are confident using Bmg in production. Systematic inspection of query
|
91
|
+
plans is suggested though. Alf was a bit too experimental to be used on
|
92
|
+
(critical) production systems.
|
93
|
+
|
94
|
+
2. Alf exposes a functional syntax, command line tool, restful tools and
|
95
|
+
many more. Bmg is limited to the core algebra, main Relation abstraction
|
96
|
+
and SQL generation.
|
97
|
+
|
98
|
+
3. Bmg is less strict regarding conformance to relational theory, and
|
99
|
+
may actually expose non relational features (such as support for null,
|
100
|
+
left_join operator, etc.). Sharp tools hurt, use them with great care.
|
101
|
+
|
102
|
+
4. Bmg does not yet implement all operators documented on try-alf.org, even
|
103
|
+
if we plan to eventually support them all.
|
104
|
+
|
105
|
+
5. Bmg has a few additional operators that prove very useful on real
|
106
|
+
production use cases: prefix, suffix, autowrap, autosummarize, left_join,
|
107
|
+
rxmatch, etc.
|
108
|
+
|
57
109
|
## Supported operators
|
58
110
|
|
59
|
-
```
|
111
|
+
```ruby
|
60
112
|
r.allbut([:a, :b, ...]) # remove specified attributes
|
61
113
|
r.autowrap(split: '_') # structure a flat relation, split: '_' is the default
|
62
114
|
r.autosummarize([:a, :b, ...], x: :sum) # (experimental) usual summarizers supported
|
@@ -66,6 +118,8 @@ r.group([:a, :b, ...], :x) # relation-valued attribute from at
|
|
66
118
|
r.image(right, :x, [:a, :b, ...]) # relation-valued attribute from another relation
|
67
119
|
r.join(right, [:a, :b, ...]) # natural join on a join key
|
68
120
|
r.join(right, :a => :x, :b => :y, ...) # natural join after right reversed renaming
|
121
|
+
r.left_join(right, [:a, :b, ...], {...}) # left join with optional default right tuple
|
122
|
+
r.left_join(right, {:a => :x, ...}, {...}) # left join after right reversed renaming
|
69
123
|
r.matching(right, [:a, :b, ...]) # semi join, aka where exists
|
70
124
|
r.matching(right, :a => :x, :b => :y, ...) # semi join, after right reversed renaming
|
71
125
|
r.not_matching(right, [:a, :b, ...]) # inverse semi join, aka where not exists
|
@@ -78,5 +132,19 @@ r.restrict(a: "foo", b: "bar", ...) # relational restriction, aka where
|
|
78
132
|
r.rxmatch([:a, :b, ...], /xxx/) # regex match kind of restriction
|
79
133
|
r.summarize([:a, :b, ...], x: :sum) # relational summarization
|
80
134
|
r.suffix(:_foo, but: [:a, ...]) # suffix kind of renaming
|
135
|
+
t.transform(:to_s) # all-attrs transformation
|
136
|
+
t.transform(&:to_s) # similar, but Proc-driven
|
137
|
+
t.transform(:foo => :upcase, ...) # specific-attrs tranformation
|
138
|
+
t.transform([:to_s, :upcase]) # chain-transformation
|
81
139
|
r.union(right) # relational union
|
82
140
|
```
|
141
|
+
|
142
|
+
## Who is behind Bmg?
|
143
|
+
|
144
|
+
Bernard Lambeau (bernard@klaro.cards) is Alf & Bmg main engineer & maintainer.
|
145
|
+
|
146
|
+
Enspirit (https://enspirit.be) and Klaro App (https://klaro.cards) are both
|
147
|
+
actively using and contributing to the library.
|
148
|
+
|
149
|
+
Feel free to contact us for help, ideas and/or contributions. Please use github
|
150
|
+
issues and pull requests if possible if code is involved.
|
data/lib/bmg.rb
CHANGED
@@ -38,11 +38,13 @@ module Bmg
|
|
38
38
|
require_relative 'bmg/operator'
|
39
39
|
|
40
40
|
require_relative 'bmg/reader'
|
41
|
+
require_relative 'bmg/writer'
|
41
42
|
|
42
43
|
require_relative 'bmg/relation/empty'
|
43
44
|
require_relative 'bmg/relation/in_memory'
|
44
45
|
require_relative 'bmg/relation/spied'
|
45
46
|
require_relative 'bmg/relation/materialized'
|
47
|
+
require_relative 'bmg/relation/proxy'
|
46
48
|
|
47
49
|
# Deprecated
|
48
50
|
Leaf = Relation::InMemory
|
data/lib/bmg/algebra.rb
CHANGED
@@ -172,6 +172,16 @@ module Bmg
|
|
172
172
|
end
|
173
173
|
protected :_summarize
|
174
174
|
|
175
|
+
def transform(transformation = nil, options = {}, &proc)
|
176
|
+
transformation, options = proc, (transformation || {}) unless proc.nil?
|
177
|
+
_transform(self.type.transform(transformation, options), transformation, options)
|
178
|
+
end
|
179
|
+
|
180
|
+
def _transform(type, transformation, options)
|
181
|
+
Operator::Transform.new(type, self, transformation, options)
|
182
|
+
end
|
183
|
+
protected :_transform
|
184
|
+
|
175
185
|
def union(other, options = {})
|
176
186
|
return self if other.is_a?(Relation::Empty)
|
177
187
|
_union self.type.union(other.type), other, options
|
@@ -37,6 +37,12 @@ module Bmg
|
|
37
37
|
self.join(right.rename(renaming), on.keys)
|
38
38
|
end
|
39
39
|
|
40
|
+
def left_join(right, on = [], *args)
|
41
|
+
return super unless on.is_a?(Hash)
|
42
|
+
renaming = Hash[on.map{|k,v| [v,k] }]
|
43
|
+
self.left_join(right.rename(renaming), on.keys, *args)
|
44
|
+
end
|
45
|
+
|
40
46
|
def matching(right, on = [])
|
41
47
|
return super unless on.is_a?(Hash)
|
42
48
|
renaming = Hash[on.map{|k,v| [v,k] }]
|
data/lib/bmg/operator.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Bmg
|
2
|
+
module Operator
|
3
|
+
#
|
4
|
+
# Transform operator.
|
5
|
+
#
|
6
|
+
# Transforms existing attributes through computations
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# [{ a: 1 }] transform { a: ->(t){ t[:a]*2 } } => [{ a: 4 }]
|
11
|
+
#
|
12
|
+
class Transform
|
13
|
+
include Operator::Unary
|
14
|
+
|
15
|
+
DEFAULT_OPTIONS = {}
|
16
|
+
|
17
|
+
def initialize(type, operand, transformation, options = {})
|
18
|
+
@type = type
|
19
|
+
@operand = operand
|
20
|
+
@transformation = transformation
|
21
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
attr_reader :transformation
|
27
|
+
|
28
|
+
public
|
29
|
+
|
30
|
+
def each
|
31
|
+
t = transformer
|
32
|
+
@operand.each do |tuple|
|
33
|
+
yield t.call(tuple)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_ast
|
38
|
+
[ :transform, operand.to_ast, transformation.dup ]
|
39
|
+
end
|
40
|
+
|
41
|
+
protected ### optimization
|
42
|
+
|
43
|
+
protected ### inspect
|
44
|
+
|
45
|
+
def args
|
46
|
+
[ transformation ]
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def transformer
|
52
|
+
@transformer ||= TupleTransformer.new(transformation)
|
53
|
+
end
|
54
|
+
|
55
|
+
end # class Transform
|
56
|
+
end # module Operator
|
57
|
+
end # module Bmg
|
data/lib/bmg/relation.rb
CHANGED
@@ -105,6 +105,20 @@ module Bmg
|
|
105
105
|
to_a.to_json(*args, &bl)
|
106
106
|
end
|
107
107
|
|
108
|
+
# Writes the relation data to CSV.
|
109
|
+
#
|
110
|
+
# `string_or_io` and `options` are what CSV::new itself
|
111
|
+
# recognizes, default options are CSV's.
|
112
|
+
#
|
113
|
+
# When no string_or_io is used, the method uses a string.
|
114
|
+
#
|
115
|
+
# The method always returns the string_or_io.
|
116
|
+
def to_csv(options = {}, string_or_io = nil, preferences = nil)
|
117
|
+
options, string_or_io = {}, options unless options.is_a?(Hash)
|
118
|
+
string_or_io, preferences = nil, string_or_io if string_or_io.is_a?(Hash)
|
119
|
+
Writer::Csv.new(options, preferences).call(self, string_or_io)
|
120
|
+
end
|
121
|
+
|
108
122
|
# Converts to an sexpr expression.
|
109
123
|
def to_ast
|
110
124
|
raise "Bmg is missing a feature!"
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Bmg
|
2
|
+
module Relation
|
3
|
+
#
|
4
|
+
# This module can be used to create typed collection on top
|
5
|
+
# of Bmg relations. Algebra methods will be delegated to the
|
6
|
+
# decorated relation, and results wrapped in a new instance
|
7
|
+
# of the class.
|
8
|
+
#
|
9
|
+
module Proxy
|
10
|
+
|
11
|
+
def initialize(relation)
|
12
|
+
@relation = relation
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(name, *args, &bl)
|
16
|
+
if @relation.respond_to?(name)
|
17
|
+
res = @relation.send(name, *args, &bl)
|
18
|
+
res.is_a?(Relation) ? _proxy(res) : res
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_to?(name, *args)
|
25
|
+
@relation.respond_to?(name) || super
|
26
|
+
end
|
27
|
+
|
28
|
+
[
|
29
|
+
:extend
|
30
|
+
].each do |name|
|
31
|
+
define_method(name) do |*args, &bl|
|
32
|
+
res = @relation.send(name, *args, &bl)
|
33
|
+
res.is_a?(Relation) ? _proxy(res) : res
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
[
|
38
|
+
:one,
|
39
|
+
:one_or_nil
|
40
|
+
].each do |meth|
|
41
|
+
define_method(meth) do |*args, &bl|
|
42
|
+
res = @relation.send(meth, *args, &bl)
|
43
|
+
res.nil? ? nil : _proxy_tuple(res)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_json(*args, &bl)
|
48
|
+
@relation.to_json(*args, &bl)
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def _proxy(relation)
|
54
|
+
self.class.new(relation)
|
55
|
+
end
|
56
|
+
|
57
|
+
def _proxy_tuple(tuple)
|
58
|
+
tuple
|
59
|
+
end
|
60
|
+
|
61
|
+
end # module Proxy
|
62
|
+
end # class Relation
|
63
|
+
end # module Bmg
|
@@ -41,7 +41,7 @@ module Bmg
|
|
41
41
|
dataset = apply(sexpr.from_clause) if sexpr.from_clause
|
42
42
|
#
|
43
43
|
selection = apply(sexpr.select_list)
|
44
|
-
predicate =
|
44
|
+
predicate = compile_predicate(sexpr.predicate) if sexpr.predicate
|
45
45
|
grouping = apply(sexpr.group_by_clause) if sexpr.group_by_clause
|
46
46
|
order = apply(sexpr.order_by_clause) if sexpr.order_by_clause
|
47
47
|
limit = apply(sexpr.limit_clause) if sexpr.limit_clause
|
@@ -109,12 +109,12 @@ module Bmg
|
|
109
109
|
elsif kind == :inner_join
|
110
110
|
options = { qualify: false, table_alias: false }
|
111
111
|
ds.join_table(:inner, apply(table), nil, options){|*args|
|
112
|
-
|
112
|
+
compile_predicate(on)
|
113
113
|
}
|
114
114
|
elsif kind == :left_join
|
115
115
|
options = { qualify: false, table_alias: false }
|
116
116
|
ds.join_table(:left, apply(table), nil, options){|*args|
|
117
|
-
|
117
|
+
compile_predicate(on)
|
118
118
|
}
|
119
119
|
else
|
120
120
|
raise IllegalArgumentError, "Unrecognized from clause: `#{sexpr}`"
|
@@ -165,22 +165,35 @@ module Bmg
|
|
165
165
|
sexpr.last
|
166
166
|
end
|
167
167
|
|
168
|
+
private
|
169
|
+
|
170
|
+
def dataset(expr)
|
171
|
+
return expr if ::Sequel::Dataset===expr
|
172
|
+
sequel_db[expr]
|
173
|
+
end
|
174
|
+
|
175
|
+
def compile_predicate(predicate)
|
176
|
+
PredicateTranslator.new(self).call(predicate)
|
177
|
+
end
|
178
|
+
|
179
|
+
class PredicateTranslator < Sexpr::Processor
|
180
|
+
include ::Predicate::ToSequel::Methods
|
181
|
+
|
182
|
+
def initialize(parent)
|
183
|
+
@parent = parent
|
184
|
+
end
|
185
|
+
|
168
186
|
public ### Predicate hack
|
169
187
|
|
170
188
|
def on_opaque(sexpr)
|
171
|
-
apply(sexpr.last)
|
189
|
+
@parent.apply(sexpr.last)
|
172
190
|
end
|
173
191
|
|
174
192
|
def on_exists(sexpr)
|
175
|
-
apply(sexpr.last).exists
|
193
|
+
@parent.apply(sexpr.last).exists
|
176
194
|
end
|
177
195
|
|
178
|
-
|
179
|
-
|
180
|
-
def dataset(expr)
|
181
|
-
return expr if ::Sequel::Dataset===expr
|
182
|
-
sequel_db[expr]
|
183
|
-
end
|
196
|
+
end
|
184
197
|
|
185
198
|
end # class Translator
|
186
199
|
end # module Sequel
|
data/lib/bmg/support.rb
CHANGED
data/lib/bmg/support/keys.rb
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Bmg
|
2
|
+
class OutputPreferences
|
3
|
+
|
4
|
+
DEFAULT_PREFS = {
|
5
|
+
attributes_ordering: nil,
|
6
|
+
extra_attributes: :after
|
7
|
+
}
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@options = DEFAULT_PREFS.merge(options)
|
11
|
+
end
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
def self.dress(arg)
|
15
|
+
return arg if arg.is_a?(OutputPreferences)
|
16
|
+
arg = {} if arg.nil?
|
17
|
+
new(arg)
|
18
|
+
end
|
19
|
+
|
20
|
+
def attributes_ordering
|
21
|
+
options[:attributes_ordering]
|
22
|
+
end
|
23
|
+
|
24
|
+
def extra_attributes
|
25
|
+
options[:extra_attributes]
|
26
|
+
end
|
27
|
+
|
28
|
+
def order_attrlist(attrlist)
|
29
|
+
return attrlist if attributes_ordering.nil?
|
30
|
+
index = Hash[attributes_ordering.each_with_index.to_a]
|
31
|
+
attrlist.sort{|a,b|
|
32
|
+
ai, bi = index[a], index[b]
|
33
|
+
if ai && bi
|
34
|
+
ai <=> bi
|
35
|
+
elsif ai
|
36
|
+
extra_attributes == :after ? -1 : 1
|
37
|
+
else
|
38
|
+
extra_attributes == :after ? 1 : -1
|
39
|
+
end
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
end # class OutputPreferences
|
44
|
+
end # module Bmg
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Bmg
|
2
|
+
class TupleTransformer
|
3
|
+
|
4
|
+
def initialize(transformation)
|
5
|
+
@transformation = transformation
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.new(arg)
|
9
|
+
return arg if arg.is_a?(TupleTransformer)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(tuple)
|
14
|
+
transform_tuple(tuple, @transformation)
|
15
|
+
end
|
16
|
+
|
17
|
+
def knows_attrlist?
|
18
|
+
@transformation.is_a?(Hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_attrlist
|
22
|
+
@transformation.keys
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def transform_tuple(tuple, with)
|
28
|
+
case with
|
29
|
+
when Symbol
|
30
|
+
tuple.each_with_object({}){|(k,v),dup|
|
31
|
+
dup[k] = transform_attr(v, with)
|
32
|
+
}
|
33
|
+
when Proc
|
34
|
+
tuple.each_with_object({}){|(k,v),dup|
|
35
|
+
dup[k] = transform_attr(v, with)
|
36
|
+
}
|
37
|
+
when Hash
|
38
|
+
with.each_with_object(tuple.dup){|(k,v),dup|
|
39
|
+
dup[k] = transform_attr(dup[k], v)
|
40
|
+
}
|
41
|
+
when Array
|
42
|
+
with.inject(tuple){|dup,on|
|
43
|
+
transform_tuple(dup, on)
|
44
|
+
}
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Unexpected transformation `#{with.inspect}`"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def transform_attr(value, with)
|
51
|
+
case with
|
52
|
+
when Symbol
|
53
|
+
value.send(with)
|
54
|
+
when Proc
|
55
|
+
with.call(value)
|
56
|
+
when Hash
|
57
|
+
with[value]
|
58
|
+
else
|
59
|
+
raise ArgumentError, "Unexpected transformation `#{with.inspect}`"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end # module TupleTransformer
|
64
|
+
end # module Bmg
|
data/lib/bmg/type.rb
CHANGED
@@ -241,6 +241,31 @@ module Bmg
|
|
241
241
|
}
|
242
242
|
end
|
243
243
|
|
244
|
+
def transform(transformation, options = {})
|
245
|
+
transformer = TupleTransformer.new(transformation)
|
246
|
+
if typechecked? && knows_attrlist? && transformer.knows_attrlist?
|
247
|
+
known_attributes!(transformer.to_attrlist)
|
248
|
+
end
|
249
|
+
keys = if options[:key_preserving]
|
250
|
+
self._keys
|
251
|
+
elsif transformer.knows_attrlist? && knows_keys?
|
252
|
+
touched_attrs = transformer.to_attrlist
|
253
|
+
keys = self._keys.select{|k| (k & touched_attrs).empty? }
|
254
|
+
else
|
255
|
+
nil
|
256
|
+
end
|
257
|
+
pred = if transformer.knows_attrlist?
|
258
|
+
attr_list = transformer.to_attrlist
|
259
|
+
predicate.and_split(attr_list).last
|
260
|
+
else
|
261
|
+
Predicate.tautology
|
262
|
+
end
|
263
|
+
dup.tap{|x|
|
264
|
+
x.keys = keys
|
265
|
+
x.predicate = pred
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
244
269
|
def union(other)
|
245
270
|
if typechecked? && knows_attrlist? && other.knows_attrlist?
|
246
271
|
missing = self.attrlist - other.attrlist
|
data/lib/bmg/version.rb
CHANGED
data/lib/bmg/writer.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'writer/csv'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Bmg
|
2
|
+
module Writer
|
3
|
+
class Csv
|
4
|
+
include Writer
|
5
|
+
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
}
|
8
|
+
|
9
|
+
def initialize(csv_options, output_preferences = nil)
|
10
|
+
@csv_options = DEFAULT_OPTIONS.merge(csv_options)
|
11
|
+
@output_preferences = OutputPreferences.dress(output_preferences)
|
12
|
+
end
|
13
|
+
attr_reader :csv_options, :output_preferences
|
14
|
+
|
15
|
+
def call(relation, string_or_io = nil)
|
16
|
+
require 'csv'
|
17
|
+
string_or_io, to_s = string_or_io.nil? ? [StringIO.new, true] : [string_or_io, false]
|
18
|
+
headers, csv = infer_headers(relation.type), nil
|
19
|
+
relation.each do |tuple|
|
20
|
+
if csv.nil?
|
21
|
+
headers = infer_headers(tuple) if headers.nil?
|
22
|
+
csv = CSV.new(string_or_io, csv_options.merge(headers: headers))
|
23
|
+
end
|
24
|
+
csv << headers.map{|h| tuple[h] }
|
25
|
+
end
|
26
|
+
to_s ? string_or_io.string : string_or_io
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def infer_headers(from)
|
32
|
+
attrlist = if from.is_a?(Type) && from.knows_attrlist?
|
33
|
+
from.to_attrlist
|
34
|
+
elsif from.is_a?(Hash)
|
35
|
+
from.keys
|
36
|
+
end
|
37
|
+
attrlist ? output_preferences.order_attrlist(attrlist) : nil
|
38
|
+
end
|
39
|
+
|
40
|
+
end # class Csv
|
41
|
+
end # module Writer
|
42
|
+
end # module Bmg
|
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.17.
|
4
|
+
version: 0.17.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: predicate
|
@@ -16,62 +16,62 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '2.
|
19
|
+
version: '2.4'
|
20
20
|
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 2.
|
22
|
+
version: 2.4.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - "~>"
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '2.
|
29
|
+
version: '2.4'
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 2.
|
32
|
+
version: 2.4.0
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: path
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - "
|
37
|
+
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
40
|
-
type: :
|
39
|
+
version: '1.3'
|
40
|
+
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
|
-
- - "
|
44
|
+
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
46
|
+
version: '1.3'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
|
-
name:
|
48
|
+
name: rake
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
53
|
+
version: '13'
|
54
54
|
type: :development
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version: '
|
60
|
+
version: '13'
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
62
|
+
name: rspec
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
|
-
- - "
|
65
|
+
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '
|
67
|
+
version: '3.6'
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
|
-
- - "
|
72
|
+
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
74
|
+
version: '3.6'
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: roo
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,6 +149,7 @@ files:
|
|
149
149
|
- lib/bmg/operator/shared/nary.rb
|
150
150
|
- lib/bmg/operator/shared/unary.rb
|
151
151
|
- lib/bmg/operator/summarize.rb
|
152
|
+
- lib/bmg/operator/transform.rb
|
152
153
|
- lib/bmg/operator/union.rb
|
153
154
|
- lib/bmg/reader.rb
|
154
155
|
- lib/bmg/reader/csv.rb
|
@@ -157,6 +158,7 @@ files:
|
|
157
158
|
- lib/bmg/relation/empty.rb
|
158
159
|
- lib/bmg/relation/in_memory.rb
|
159
160
|
- lib/bmg/relation/materialized.rb
|
161
|
+
- lib/bmg/relation/proxy.rb
|
160
162
|
- lib/bmg/relation/spied.rb
|
161
163
|
- lib/bmg/sequel.rb
|
162
164
|
- lib/bmg/sequel/ext.rb
|
@@ -258,9 +260,13 @@ files:
|
|
258
260
|
- lib/bmg/summarizer/variance.rb
|
259
261
|
- lib/bmg/support.rb
|
260
262
|
- lib/bmg/support/keys.rb
|
263
|
+
- lib/bmg/support/output_preferences.rb
|
261
264
|
- lib/bmg/support/tuple_algebra.rb
|
265
|
+
- lib/bmg/support/tuple_transformer.rb
|
262
266
|
- lib/bmg/type.rb
|
263
267
|
- lib/bmg/version.rb
|
268
|
+
- lib/bmg/writer.rb
|
269
|
+
- lib/bmg/writer/csv.rb
|
264
270
|
- tasks/gem.rake
|
265
271
|
- tasks/test.rake
|
266
272
|
homepage: http://github.com/enspirit/bmg
|