bmg 0.21.0 → 0.21.3

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: ef6b320621feeff681bb76b804da274ae055573c
4
- data.tar.gz: 2a119b1aa5838fbb2b657332de25fd22cc10dbb6
3
+ metadata.gz: 83f3d7ebfe5587af85b9823b7ab5a9d11d6e9b30
4
+ data.tar.gz: 34418e3d5ccfe156c3b0320e6821030a09ba1cdc
5
5
  SHA512:
6
- metadata.gz: e33cda560cb1c3f119940745c63f45884a406d7b0bce113d08a43d2f374a9a658ab47667c63919d16d5863f6867f2d29808d23a049efe9e34487a282e29c9c82
7
- data.tar.gz: ec437125ffbf4e41633b33c6ddc5eedeccef915dd2201f7d0f2a54e8ba348f272e67195ebc57cd8a8e839b07a0fa1aacb957eacf6723d34a42b03dac1d4783ec
6
+ metadata.gz: f1e68f36ad0cd1a5c9ba2d2dd5fdcd8f02220c3588e5ef995439db58c46dab2bb6fa91ff249f3541ae2096e9d12f3c0d9a9d424c99310c7e90fef7cd4f460994
7
+ data.tar.gz: 0c9c97791462f47799be4ae510d5be7b0aa3c30360592899fbfb336f31c25762d8fe5d35d87202413f85916ce8fc9199c48d40a26a22b480fc72d497edbe1f1d
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 ruby library. It implements the
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, excel, text)](#reading-files-csv-excel-text)
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, excel, text)
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 ruby's standard
132
+ Options are directly transmitted to `::CSV.new`, check Ruby's standard
133
133
  library.
134
134
 
135
135
  #### Excel files
@@ -302,7 +302,7 @@ r.where(predicate) # alias for restrict(predicate)
302
302
  .where(...)
303
303
  ```
304
304
 
305
- 2. Bmg supports in memory relations, json relations, csv relations, SQL
305
+ 2. Bmg supports in-memory relations, JSON relations, csv relations, SQL
306
306
  relations and so on. It's not tight to SQL generation, and supports
307
307
  queries accross multiple data sources.
308
308
 
@@ -312,8 +312,8 @@ r.where(predicate) # alias for restrict(predicate)
312
312
  4. Bmg supports various *structuring* operators (group, image, autowrap,
313
313
  autosummarize, etc.) and allows building 'non flat' relations.
314
314
 
315
- 5. Bmg can use full ruby power when that helps (e.g. regular expressions in
316
- WHERE clauses or ruby code in EXTEND clauses). This may prevent Bmg from
315
+ 5. Bmg can use full Ruby power when that helps (e.g. regular expressions in
316
+ WHERE clauses or Ruby code in EXTEND clauses). This may prevent Bmg from
317
317
  delegating work to underlying data sources (e.g. SQL server) and should
318
318
  therefore be used with care though.
319
319
 
@@ -323,14 +323,14 @@ If you use Alf (or used it in the past), below are the main differences between
323
323
  Bmg and Alf. Bmg has NOT been written to be API-compatible with Alf and will
324
324
  probably never be.
325
325
 
326
- 1. Bmg's implementation is much simpler than Alf and uses no ruby core
326
+ 1. Bmg's implementation is much simpler than Alf and uses no Ruby core
327
327
  extention.
328
328
 
329
329
  2. We are confident using Bmg in production. Systematic inspection of query
330
330
  plans is advised though. Alf was a bit too experimental to be used on
331
331
  (critical) production systems.
332
332
 
333
- 3. Alf exposes a functional syntax, command line tool, restful tools and
333
+ 3. Alf exposes a functional syntax, command-line tool, restful tools and
334
334
  many more. Bmg is limited to the core algebra, main Relation abstraction
335
335
  and SQL generation.
336
336
 
@@ -70,6 +70,29 @@ module Bmg
70
70
  end
71
71
  end
72
72
 
73
+ def _restrict(type, predicate)
74
+ return super if left_join?
75
+
76
+ # right_p makes no reference to attributes in left => full on right
77
+ # left_p makes no reference to attributes in right => full on left
78
+ up1, right_p = predicate.and_split(left.type.attrlist! - on)
79
+ up2, left_p = predicate.and_split(right.type.attrlist! - on)
80
+ if right_p.tautology? && left_p.tautology?
81
+ # no optimization can be done
82
+ super
83
+ else
84
+ # remains the full reduction, that up2 is not, since we got it
85
+ # from predicate, not from up1
86
+ remains, _ = up1.and_split(right.type.attrlist! - on)
87
+ left
88
+ .restrict(left_p)
89
+ .join(right.restrict(right_p), on)
90
+ .restrict(remains)
91
+ end
92
+ rescue UnknownAttributesError
93
+ super
94
+ end
95
+
73
96
  def _unautowrap(operand, options)
74
97
  return [operand, false] unless operand.is_a?(Operator::Autowrap)
75
98
  return [operand, false] unless operand.same_options?(options)
@@ -51,7 +51,7 @@ module Bmg
51
51
  protected
52
52
 
53
53
  def _restrict(type, predicate)
54
- Restrict.new(type, @operand, @predicate & predicate)
54
+ @operand.restrict(@predicate & predicate)
55
55
  end
56
56
 
57
57
  protected ### inspect
@@ -39,7 +39,9 @@ module Bmg
39
39
  private
40
40
 
41
41
  def tuple(row)
42
- row.to_hash.each_with_object({}){|(k,v),h| h[k.to_sym] = v }
42
+ row.to_hash.each_with_object({}){|(k,v),h|
43
+ h[k.to_sym] = v
44
+ }
43
45
  end
44
46
 
45
47
  def handle_type(type)
@@ -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.sort{|a,b|
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/version.rb CHANGED
@@ -2,7 +2,7 @@ module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 21
5
- TINY = 0
5
+ TINY = 3
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
@@ -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
- relation.each do |tuple|
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
@@ -6,11 +6,11 @@ module Bmg
6
6
  DEFAULT_OPTIONS = {
7
7
  }
8
8
 
9
- def initialize(csv_options, output_preferences = nil)
10
- @csv_options = DEFAULT_OPTIONS.merge(csv_options)
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 :csv_options, :output_preferences
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
- relation.each_with_index do |tuple,i|
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.0
4
+ version: 0.21.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-18 00:00:00.000000000 Z
11
+ date: 2024-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: predicate