bmg 0.17.3 → 0.17.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
  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