bmg 0.21.2 → 0.21.4

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: c0ca5faec7c7529e032eca725661a0e480b0e527
4
- data.tar.gz: b2891c92b61211b40048d8545988bc1de5fd5bfc
3
+ metadata.gz: 59da4b61f1d6c332de148552eab6277032df750c
4
+ data.tar.gz: b65877260b6a5eca59fb864f52a08dad26f917f4
5
5
  SHA512:
6
- metadata.gz: 399c045c70776108049673574a0f7631dc6b3b70e879b180de00feab49bd4ec4553b991a04549c4a336741cbadd3195204d918d1de5b060ee3195748d7bd2446
7
- data.tar.gz: 97304f0d053a2755bfc0a15504610e917686e86f5b8fa7871ff09f66446a6b7cef9aa7043f68637ee5d72ac394838f773522dcfb4a175b2cc7443bb723cb279c
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 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
@@ -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 memory relations, json relations, csv relations, SQL
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 ruby power when that helps (e.g. regular expressions in
316
- WHERE clauses or ruby code in EXTEND clauses). This may prevent Bmg from
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 ruby core
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 line tool, restful tools and
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] }]
@@ -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/type.rb CHANGED
@@ -310,7 +310,7 @@ module Bmg
310
310
  }
311
311
  end
312
312
 
313
- private
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
@@ -2,7 +2,7 @@ module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 21
5
- TINY = 2
5
+ TINY = 4
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.2
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: 2023-10-09 00:00:00.000000000 Z
11
+ date: 2024-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: predicate