bmg 0.17.3 → 0.17.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90ed0f35cc6ced83ada1d6aeb3162b7fb8c1bb3f6f20f870f030c589e991e97e
4
- data.tar.gz: 959415999253759d072fae5e5280417cdbd5fd8774bbf90e541b74f2324d197c
3
+ metadata.gz: 1e48d4a2d3c4bea8271e40ad4db52f43438de7f754079cc4c5bcd5e98ce4865d
4
+ data.tar.gz: 3fff9f251a2b766ab3de631748ccc6fe6f6b96ccef88507cb3ed76766fea3992
5
5
  SHA512:
6
- metadata.gz: fbb3cb7ea042873933e6c15f5ee73084980066cc3ee8c8c58c5169b3221a1aa4c2d97a1521618c456da0d34097df456ef34081a8a418429d1627ed2ada36c1ec
7
- data.tar.gz: 687eed5fc05caa9c7dba8258aeb70b929877d6c56c45212ac04358df562acf4ffdc57f199c8d774d43631f82088fa70cac78b1754ae2bfbdd8eaa4d61b156014
6
+ metadata.gz: facfbb798258cfdec3803cc7b93999d23c8a0776ead53c074266f4f658a27feee1a3e1dcc1145e7ad45bb03579d22f5d65108152516ac0af0d79aa59805ee016
7
+ data.tar.gz: 10271e608276bc2e00ccee3d6a94287cb2211d945898cb9cfa54764a05027c9e6a6c34a11e9725f9ed47df4df537fe054d8c3f17b24f58da9713761121523e73
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
- puts suppliers
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
@@ -80,3 +134,13 @@ r.summarize([:a, :b, ...], x: :sum) # relational summarization
80
134
  r.suffix(:_foo, but: [:a, ...]) # suffix kind of renaming
81
135
  r.union(right) # relational union
82
136
  ```
137
+
138
+ ## Who is behind Bmg?
139
+
140
+ Bernard Lambeau (bernard@klaro.cards) is Alf & Bmg main engineer & maintainer.
141
+
142
+ Enspirit (https://enspirit.be) and Klaro App (https://klaro.cards) are both
143
+ actively using and contributing to the library.
144
+
145
+ Feel free to contact us for help, ideas and/or contributions. Please use github
146
+ issues and pull requests if possible if code is involved.
@@ -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] }]
@@ -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 = apply(sexpr.predicate) if sexpr.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
- apply(on)
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
- apply(on)
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
- private
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
@@ -15,6 +15,10 @@ module Bmg
15
15
  last
16
16
  end
17
17
 
18
+ def is_computed?
19
+ false
20
+ end
21
+
18
22
  def to_sql(buffer, dialect)
19
23
  buffer << dialect.quote_identifier(last)
20
24
  buffer
@@ -11,6 +11,10 @@ module Bmg
11
11
  self[2..-1]
12
12
  end
13
13
 
14
+ def is_computed?
15
+ true
16
+ end
17
+
14
18
  def to_sql(buffer, dialect)
15
19
  buffer << summary_name.upcase << "("
16
20
  buffer << func_args.map{|fa| fa.to_sql("", dialect) }.join(', ')
@@ -8,6 +8,10 @@ module Bmg
8
8
  nil
9
9
  end
10
10
 
11
+ def is_computed?
12
+ false
13
+ end
14
+
11
15
  def to_sql(buffer, dialect)
12
16
  to_sql_literal(buffer, last)
13
17
  buffer
@@ -20,6 +20,10 @@ module Bmg
20
20
  as_name
21
21
  end
22
22
 
23
+ def is_computed?
24
+ false
25
+ end
26
+
23
27
  def to_sql(buffer, dialect)
24
28
  self[1].to_sql(buffer, dialect)
25
29
  buffer << '.'
@@ -27,6 +27,10 @@ module Bmg
27
27
  set_quantifier.all?
28
28
  end
29
29
 
30
+ def has_computed_attributes?
31
+ select_list.has_computed_attributes?
32
+ end
33
+
30
34
  def join?
31
35
  from_clause && from_clause.join?
32
36
  end
@@ -23,6 +23,10 @@ module Bmg
23
23
  last.as_name
24
24
  end
25
25
 
26
+ def is_computed?
27
+ left.is_computed?
28
+ end
29
+
26
30
  def to_sql(buffer, dialect)
27
31
  self[1].to_sql(buffer, dialect)
28
32
  unless would_be_name == as_name
@@ -23,6 +23,10 @@ module Bmg
23
23
  Builder::IS_TABLE_DEE == self
24
24
  end
25
25
 
26
+ def has_computed_attributes?
27
+ sexpr_body.any?{|item| item.is_computed? }
28
+ end
29
+
26
30
  def knows?(as_name)
27
31
  find_child{|child| child.as_name == as_name }
28
32
  end
@@ -11,6 +11,10 @@ module Bmg
11
11
  }
12
12
  end
13
13
 
14
+ def has_computed_attributes?
15
+ false
16
+ end
17
+
14
18
  def to_sql(buffer, dialect)
15
19
  last.to_sql(buffer, dialect)
16
20
  buffer << DOT << STAR
@@ -11,6 +11,10 @@ module Bmg
11
11
  self.last
12
12
  end
13
13
 
14
+ def is_computed?
15
+ true
16
+ end
17
+
14
18
  def to_sql(buffer, dialect)
15
19
  buffer << summary_func.upcase << "("
16
20
  summary_expr.to_sql(buffer, dialect)
@@ -20,7 +20,7 @@ module Bmg
20
20
  end
21
21
 
22
22
  def on_select_exp(sexpr)
23
- if sexpr.group_by_clause
23
+ if sexpr.group_by_clause || sexpr.has_computed_attributes?
24
24
  sexpr = builder.from_self(sexpr)
25
25
  call(sexpr)
26
26
  else
@@ -2,7 +2,7 @@ module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 17
5
- TINY = 3
5
+ TINY = 4
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  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.17.3
4
+ version: 0.17.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: 2020-07-09 00:00:00.000000000 Z
11
+ date: 2020-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: predicate
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.3'
19
+ version: '2.4'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 2.3.3
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.3'
29
+ version: '2.4'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 2.3.3
32
+ version: 2.4.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: rake
35
35
  requirement: !ruby/object:Gem::Requirement