bmg 0.21.2 → 0.21.4
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 +10 -9
- data/lib/bmg/algebra/shortcuts.rb +8 -0
- data/lib/bmg/reader/csv.rb +3 -1
- data/lib/bmg/support/ordering.rb +11 -1
- data/lib/bmg/support/output_preferences.rb +27 -1
- data/lib/bmg/type.rb +8 -1
- data/lib/bmg/version.rb +1 -1
- data/lib/bmg/writer/csv.rb +3 -1
- data/lib/bmg/writer/xlsx.rb +9 -7
- data/lib/bmg/writer.rb +11 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59da4b61f1d6c332de148552eab6277032df750c
|
4
|
+
data.tar.gz: b65877260b6a5eca59fb864f52a08dad26f917f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd85e6e179eb6e1a4d9ffe2f074db3c0d856d239bc65b8d535d6d14cff987a0af1eed75046aed0538726ff87925b9a2a400153a1acec7d7ecc3cc7fdc50d0e87
|
7
|
+
data.tar.gz: 5954aad879e293d34eb175ff54055473e3a983e6a16ec816694eb0b16fd90897cbf9d31bcdf53bfdef664891abb42cb538c7962954d76331c291411e816c956b
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Bmg, a relational algebra (Alf's successor)!
|
2
2
|
|
3
|
-
Bmg is a relational algebra implemented as a
|
3
|
+
Bmg is a relational algebra implemented as a Ruby library. It implements the
|
4
4
|
[Relation as First-Class Citizen](http://www.try-alf.org/blog/2013-10-21-relations-as-first-class-citizen)
|
5
5
|
paradigm contributed with [Alf](http://www.try-alf.org/) a few years ago.
|
6
6
|
|
@@ -15,7 +15,7 @@ further down this README.
|
|
15
15
|
* [Where are base relations coming from?](#where-are-base-relations-coming-from)
|
16
16
|
* [Memory relations](#memory-relations)
|
17
17
|
* [Connecting to SQL databases](#connecting-to-sql-databases)
|
18
|
-
* [Reading files (csv,
|
18
|
+
* [Reading files (csv, Excel, text)](#reading-files-csv-excel-text)
|
19
19
|
* [Connecting to Redis databases](#connecting-to-redis-databases)
|
20
20
|
* [Your own relations](#your-own-relations)
|
21
21
|
* [List of supported operators](#supported-operators)
|
@@ -117,7 +117,7 @@ Bmg.sequel(:suppliers, sequel_db)
|
|
117
117
|
# {:array=>false})
|
118
118
|
```
|
119
119
|
|
120
|
-
### Reading files (csv,
|
120
|
+
### Reading files (csv, Excel, text)
|
121
121
|
|
122
122
|
Bmg provides simple adapters to read files and reach Relationland as soon as
|
123
123
|
possible.
|
@@ -129,7 +129,7 @@ csv_options = { col_sep: ",", quote_char: '"' }
|
|
129
129
|
r = Bmg.csv("path/to/a/file.csv", csv_options)
|
130
130
|
```
|
131
131
|
|
132
|
-
Options are directly transmitted to `::CSV.new`, check
|
132
|
+
Options are directly transmitted to `::CSV.new`, check Ruby's standard
|
133
133
|
library.
|
134
134
|
|
135
135
|
#### Excel files
|
@@ -245,6 +245,7 @@ r.allbut([:a, :b, ...]) # remove specified attributes
|
|
245
245
|
r.autowrap(split: '_') # structure a flat relation, split: '_' is the default
|
246
246
|
r.autosummarize([:a, :b, ...], x: :sum) # (experimental) usual summarizers supported
|
247
247
|
r.constants(x: 12, ...) # add constant attributes (sometimes useful in unions)
|
248
|
+
r.cross_product(right) # cross product, alias `cross_join`
|
248
249
|
r.extend(x: ->(t){ ... }, ...) # add computed attributes
|
249
250
|
r.extend(x: :y) # shortcut for r.extend(x: ->(t){ t[:y] })
|
250
251
|
r.exclude(predicate) # shortcut for restrict(!predicate)
|
@@ -302,7 +303,7 @@ r.where(predicate) # alias for restrict(predicate)
|
|
302
303
|
.where(...)
|
303
304
|
```
|
304
305
|
|
305
|
-
2. Bmg supports in
|
306
|
+
2. Bmg supports in-memory relations, JSON relations, csv relations, SQL
|
306
307
|
relations and so on. It's not tight to SQL generation, and supports
|
307
308
|
queries accross multiple data sources.
|
308
309
|
|
@@ -312,8 +313,8 @@ r.where(predicate) # alias for restrict(predicate)
|
|
312
313
|
4. Bmg supports various *structuring* operators (group, image, autowrap,
|
313
314
|
autosummarize, etc.) and allows building 'non flat' relations.
|
314
315
|
|
315
|
-
5. Bmg can use full
|
316
|
-
WHERE clauses or
|
316
|
+
5. Bmg can use full Ruby power when that helps (e.g. regular expressions in
|
317
|
+
WHERE clauses or Ruby code in EXTEND clauses). This may prevent Bmg from
|
317
318
|
delegating work to underlying data sources (e.g. SQL server) and should
|
318
319
|
therefore be used with care though.
|
319
320
|
|
@@ -323,14 +324,14 @@ If you use Alf (or used it in the past), below are the main differences between
|
|
323
324
|
Bmg and Alf. Bmg has NOT been written to be API-compatible with Alf and will
|
324
325
|
probably never be.
|
325
326
|
|
326
|
-
1. Bmg's implementation is much simpler than Alf and uses no
|
327
|
+
1. Bmg's implementation is much simpler than Alf and uses no Ruby core
|
327
328
|
extention.
|
328
329
|
|
329
330
|
2. We are confident using Bmg in production. Systematic inspection of query
|
330
331
|
plans is advised though. Alf was a bit too experimental to be used on
|
331
332
|
(critical) production systems.
|
332
333
|
|
333
|
-
3. Alf exposes a functional syntax, command
|
334
|
+
3. Alf exposes a functional syntax, command-line tool, restful tools and
|
334
335
|
many more. Bmg is limited to the core algebra, main Relation abstraction
|
335
336
|
and SQL generation.
|
336
337
|
|
@@ -57,6 +57,14 @@ module Bmg
|
|
57
57
|
self.left_join(right.rename(renaming), on.keys, *args)
|
58
58
|
end
|
59
59
|
|
60
|
+
def cross_product(right)
|
61
|
+
return join(right) unless self.type.typechecked? || right.type.typechecked?
|
62
|
+
return join(right) unless self.type.knows_attrlist? && right.type.knows_attrlist?
|
63
|
+
|
64
|
+
self.type.cross_join_compatible!(right)
|
65
|
+
return join(right)
|
66
|
+
end
|
67
|
+
|
60
68
|
def matching(right, on = [])
|
61
69
|
return super unless on.is_a?(Hash)
|
62
70
|
renaming = Hash[on.map{|k,v| [v,k] }]
|
data/lib/bmg/reader/csv.rb
CHANGED
data/lib/bmg/support/ordering.rb
CHANGED
@@ -2,7 +2,13 @@ module Bmg
|
|
2
2
|
class Ordering
|
3
3
|
|
4
4
|
def initialize(attrs)
|
5
|
-
@attrs = attrs
|
5
|
+
@attrs = if attrs.empty?
|
6
|
+
[]
|
7
|
+
elsif attrs.first.is_a?(Symbol)
|
8
|
+
attrs.map{|a| [a, :asc] }
|
9
|
+
else
|
10
|
+
attrs
|
11
|
+
end
|
6
12
|
end
|
7
13
|
attr_reader :attrs
|
8
14
|
|
@@ -33,5 +39,9 @@ module Bmg
|
|
33
39
|
0
|
34
40
|
end
|
35
41
|
|
42
|
+
def to_a
|
43
|
+
attrs.to_a
|
44
|
+
end
|
45
|
+
|
36
46
|
end # class Ordering
|
37
47
|
end # module Bmg
|
@@ -3,6 +3,7 @@ module Bmg
|
|
3
3
|
|
4
4
|
DEFAULT_PREFS = {
|
5
5
|
attributes_ordering: nil,
|
6
|
+
tuple_ordering: nil,
|
6
7
|
extra_attributes: :after
|
7
8
|
}
|
8
9
|
|
@@ -17,6 +18,12 @@ module Bmg
|
|
17
18
|
new(arg)
|
18
19
|
end
|
19
20
|
|
21
|
+
def tuple_ordering
|
22
|
+
return nil unless to = options[:tuple_ordering]
|
23
|
+
|
24
|
+
@tuple_ordering = Ordering.new(to)
|
25
|
+
end
|
26
|
+
|
20
27
|
def attributes_ordering
|
21
28
|
options[:attributes_ordering]
|
22
29
|
end
|
@@ -25,10 +32,29 @@ module Bmg
|
|
25
32
|
options[:extra_attributes]
|
26
33
|
end
|
27
34
|
|
35
|
+
def grouping_attributes
|
36
|
+
options[:grouping_attributes]
|
37
|
+
end
|
38
|
+
|
39
|
+
def erase_redundance_in_group(before, current)
|
40
|
+
return [nil, current] unless ga = grouping_attributes
|
41
|
+
return [current, current] unless before
|
42
|
+
|
43
|
+
new_before, new_current = current.dup, current.dup
|
44
|
+
ga.each do |attr|
|
45
|
+
return [new_before, new_current] unless before[attr] == current[attr]
|
46
|
+
new_current[attr] = nil
|
47
|
+
end
|
48
|
+
[new_before, new_current]
|
49
|
+
end
|
50
|
+
|
28
51
|
def order_attrlist(attrlist)
|
29
52
|
return attrlist if attributes_ordering.nil?
|
53
|
+
|
30
54
|
index = Hash[attributes_ordering.each_with_index.to_a]
|
31
|
-
attrlist
|
55
|
+
base = attrlist
|
56
|
+
base = attrlist & attributes_ordering if extra_attributes == :ignored
|
57
|
+
base.sort{|a,b|
|
32
58
|
ai, bi = index[a], index[b]
|
33
59
|
if ai && bi
|
34
60
|
ai <=> bi
|
data/lib/bmg/type.rb
CHANGED
@@ -310,7 +310,7 @@ module Bmg
|
|
310
310
|
}
|
311
311
|
end
|
312
312
|
|
313
|
-
|
313
|
+
public
|
314
314
|
|
315
315
|
def known_attributes!(attrs)
|
316
316
|
extra = attrs - self.attrlist
|
@@ -331,5 +331,12 @@ module Bmg
|
|
331
331
|
end
|
332
332
|
end
|
333
333
|
|
334
|
+
def cross_join_compatible!(right)
|
335
|
+
shared = self.attrlist & right.type.attrlist
|
336
|
+
unless shared.empty?
|
337
|
+
raise TypeError, "Cross product incompatible — duplicate attribute(s): #{shared.join(', ')}"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
334
341
|
end # class Type
|
335
342
|
end # module Bmg
|
data/lib/bmg/version.rb
CHANGED
data/lib/bmg/writer/csv.rb
CHANGED
@@ -16,12 +16,14 @@ module Bmg
|
|
16
16
|
require 'csv'
|
17
17
|
string_or_io, to_s = string_or_io.nil? ? [StringIO.new, true] : [string_or_io, false]
|
18
18
|
headers, csv = infer_headers(relation.type), nil
|
19
|
-
|
19
|
+
previous = nil
|
20
|
+
each_tuple(relation) do |tuple,i|
|
20
21
|
if csv.nil?
|
21
22
|
headers = infer_headers(tuple) if headers.nil?
|
22
23
|
csv_opts = csv_options.merge(headers: headers)
|
23
24
|
csv = CSV.new(string_or_io, **csv_opts)
|
24
25
|
end
|
26
|
+
previous, tuple = output_preferences.erase_redundance_in_group(previous, tuple)
|
25
27
|
csv << headers.map{|h| tuple[h] }
|
26
28
|
end
|
27
29
|
to_s ? string_or_io.string : string_or_io
|
data/lib/bmg/writer/xlsx.rb
CHANGED
@@ -6,11 +6,11 @@ module Bmg
|
|
6
6
|
DEFAULT_OPTIONS = {
|
7
7
|
}
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@
|
9
|
+
def initialize(xlsx_options, output_preferences = nil)
|
10
|
+
@xlsx_options = DEFAULT_OPTIONS.merge(xlsx_options)
|
11
11
|
@output_preferences = OutputPreferences.dress(output_preferences)
|
12
12
|
end
|
13
|
-
attr_reader :
|
13
|
+
attr_reader :xlsx_options, :output_preferences
|
14
14
|
|
15
15
|
def call(relation, path)
|
16
16
|
require 'write_xlsx'
|
@@ -21,22 +21,24 @@ module Bmg
|
|
21
21
|
attr_reader :workbook, :worksheet
|
22
22
|
|
23
23
|
def _call(relation, path)
|
24
|
-
@workbook = WriteXLSX.new(path)
|
25
|
-
@worksheet = workbook.add_worksheet
|
24
|
+
@workbook = xlsx_options[:workbook] || WriteXLSX.new(path)
|
25
|
+
@worksheet = xlsx_options[:worksheet] || workbook.add_worksheet
|
26
26
|
|
27
27
|
headers = infer_headers(relation.type)
|
28
|
-
|
28
|
+
before = nil
|
29
|
+
each_tuple(relation) do |tuple,i|
|
29
30
|
headers = infer_headers(tuple) if headers.nil?
|
30
31
|
headers.each_with_index do |h,i|
|
31
32
|
worksheet.write_string(0, i, h)
|
32
33
|
end if i == 0
|
34
|
+
before, tuple = output_preferences.erase_redundance_in_group(before, tuple)
|
33
35
|
headers.each_with_index do |h,j|
|
34
36
|
meth, *args = write_pair(tuple[h])
|
35
37
|
worksheet.send(meth, 1+i, j, *args)
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
workbook.close
|
41
|
+
workbook.close unless xlsx_options[:workbook]
|
40
42
|
path
|
41
43
|
end
|
42
44
|
|
data/lib/bmg/writer.rb
CHANGED
@@ -12,6 +12,17 @@ module Bmg
|
|
12
12
|
attrlist ? output_preferences.order_attrlist(attrlist) : nil
|
13
13
|
end
|
14
14
|
|
15
|
+
def each_tuple(relation, &bl)
|
16
|
+
if ordering = output_preferences.tuple_ordering
|
17
|
+
relation
|
18
|
+
.to_a
|
19
|
+
.sort{|t1,t2| ordering.compare_attrs(t1, t2) }
|
20
|
+
.each_with_index(&bl)
|
21
|
+
else
|
22
|
+
relation.each_with_index(&bl)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
15
26
|
end # module Writer
|
16
27
|
end # module Bmg
|
17
28
|
require_relative 'writer/csv'
|
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.21.
|
4
|
+
version: 0.21.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: predicate
|