alf-sequel 0.14.0 → 0.15.0

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.
Files changed (45) hide show
  1. data/Gemfile +10 -7
  2. data/Gemfile.lock +26 -14
  3. data/README.md +45 -3
  4. data/lib/alf/sequel.rb +1 -0
  5. data/lib/alf/sequel/cog.rb +23 -75
  6. data/lib/alf/sequel/compiler.rb +8 -198
  7. data/lib/alf/sequel/connection.rb +5 -1
  8. data/lib/alf/sequel/connection/connection_methods.rb +4 -2
  9. data/lib/alf/sequel/connection/schema_methods.rb +2 -6
  10. data/lib/alf/sequel/connection/update_methods.rb +1 -1
  11. data/lib/alf/sequel/loader.rb +1 -0
  12. data/lib/alf/sequel/translator.rb +198 -0
  13. data/lib/alf/sequel/unit_of_work/insert.rb +10 -4
  14. data/lib/alf/sequel/unit_of_work/update.rb +3 -3
  15. data/lib/alf/sequel/version.rb +1 -1
  16. data/spec/connection/test_heading.rb +1 -1
  17. data/spec/{fixtures/sap.db → sap.db} +0 -0
  18. data/spec/spec_helper.rb +48 -6
  19. data/spec/unit_of_work/delete/test_delete.rb +2 -2
  20. data/spec/unit_of_work/insert/test_run.rb +12 -14
  21. data/spec/unit_of_work/update/test_run.rb +1 -1
  22. data/tasks/bench.rake +40 -0
  23. data/tasks/test.rake +3 -3
  24. metadata +29 -29
  25. data/lib/alf/sequel/compiler/predicate.rb +0 -76
  26. data/spec/alf.db +0 -0
  27. data/spec/compiler/test_clip.rb +0 -40
  28. data/spec/compiler/test_compact.rb +0 -18
  29. data/spec/compiler/test_extend.rb +0 -16
  30. data/spec/compiler/test_frame.rb +0 -89
  31. data/spec/compiler/test_intersect.rb +0 -18
  32. data/spec/compiler/test_join.rb +0 -26
  33. data/spec/compiler/test_leaf_operand.rb +0 -24
  34. data/spec/compiler/test_matching.rb +0 -34
  35. data/spec/compiler/test_not_matching.rb +0 -34
  36. data/spec/compiler/test_page.rb +0 -86
  37. data/spec/compiler/test_predicate.rb +0 -141
  38. data/spec/compiler/test_project.rb +0 -64
  39. data/spec/compiler/test_rename.rb +0 -26
  40. data/spec/compiler/test_restrict.rb +0 -48
  41. data/spec/compiler/test_sort.rb +0 -18
  42. data/spec/compiler/test_union.rb +0 -18
  43. data/spec/compiler_helper.rb +0 -34
  44. data/spec/fixtures/sap.rb +0 -43
  45. data/tasks/fixtures.rake +0 -12
data/Gemfile CHANGED
@@ -1,8 +1,15 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
+ group :development do
4
+ gem "rake", "~> 10.1"
5
+ gem "rspec", "~> 2.14"
6
+ end
7
+
3
8
  group :runtime do
4
- gem "alf-core", "~> 0.14.0"
5
- gem "sequel", "~> 3.48"
9
+ gem "sequel", "~> 4.2"
10
+
11
+ gem "alf-core", path: "../alf-core"
12
+ gem "alf-sql", path: "../alf-sql"
6
13
  end
7
14
 
8
15
  group :test do
@@ -10,9 +17,5 @@ group :test do
10
17
  gem "rspec", "~> 2.14"
11
18
  gem "sqlite3", "~> 1.3", :platforms => ['mri', 'rbx']
12
19
  gem "jdbc-sqlite3", "~> 3.7", :platforms => ['jruby']
13
- end
14
-
15
- group :development do
16
- gem "rake", "~> 10.1"
17
- gem "rspec", "~> 2.14"
20
+ gem "pg"
18
21
  end
data/Gemfile.lock CHANGED
@@ -1,38 +1,50 @@
1
- GEM
2
- remote: http://rubygems.org/
1
+ PATH
2
+ remote: ../alf-core
3
3
  specs:
4
- alf-core (0.14.0)
4
+ alf-core (0.15.0)
5
5
  domain (~> 1.0)
6
6
  myrrha (~> 3.0)
7
7
  path (~> 1.3)
8
- sexpr (~> 0.5.1)
8
+ sexpr (~> 0.6.0)
9
+
10
+ PATH
11
+ remote: ../alf-sql
12
+ specs:
13
+ alf-sql (0.15.0)
14
+ alf-core (~> 0.15.0)
15
+ sexpr (~> 0.6.0)
16
+
17
+ GEM
18
+ remote: http://rubygems.org/
19
+ specs:
9
20
  diff-lcs (1.2.4)
10
21
  domain (1.0.0)
11
- jdbc-sqlite3 (3.7.2.1)
12
22
  myrrha (3.0.0)
13
23
  domain (~> 1.0)
14
24
  path (1.3.3)
25
+ pg (0.17.0)
15
26
  rake (10.1.0)
16
27
  rspec (2.14.1)
17
28
  rspec-core (~> 2.14.0)
18
29
  rspec-expectations (~> 2.14.0)
19
30
  rspec-mocks (~> 2.14.0)
20
- rspec-core (2.14.4)
21
- rspec-expectations (2.14.0)
31
+ rspec-core (2.14.7)
32
+ rspec-expectations (2.14.3)
22
33
  diff-lcs (>= 1.1.3, < 2.0)
23
- rspec-mocks (2.14.1)
24
- sequel (3.48.0)
25
- sexpr (0.5.1)
26
- sqlite3 (1.3.7)
34
+ rspec-mocks (2.14.4)
35
+ sequel (4.3.0)
36
+ sexpr (0.6.0)
37
+ sqlite3 (1.3.8)
27
38
 
28
39
  PLATFORMS
29
- java
30
40
  ruby
31
41
 
32
42
  DEPENDENCIES
33
- alf-core (~> 0.14.0)
43
+ alf-core!
44
+ alf-sql!
34
45
  jdbc-sqlite3 (~> 3.7)
46
+ pg
35
47
  rake (~> 10.1)
36
48
  rspec (~> 2.14)
37
- sequel (~> 3.48)
49
+ sequel (~> 4.2)
38
50
  sqlite3 (~> 1.3)
data/README.md CHANGED
@@ -3,9 +3,51 @@
3
3
  [![Build Status](https://secure.travis-ci.org/alf-tool/alf-sequel.png)](http://travis-ci.org/alf-tool/alf-sequel)
4
4
  [![Dependency Status](https://gemnasium.com/alf-tool/alf-sequel.png)](https://gemnasium.com/alf-tool/alf-sequel)
5
5
 
6
- A sequel adapter for alf
6
+ A DBMS adapter for Alf built atop [Sequel](http://sequel.rubyforge.org/).
7
7
 
8
8
  ## Links
9
9
 
10
- http://github.com/alf-tool/alf
11
- http://github.com/alf-tool/alf-sequel
10
+ * http://github.com/alf-tool/alf
11
+ * http://github.com/alf-tool/alf-sql
12
+ * http://github.com/alf-tool/alf-sequel
13
+
14
+ ## Synopsis
15
+
16
+ Extends [alf-sql](https://github.com/alf-tool/alf-sql#synopsis) in such a way
17
+ that most existing SQL databases can be used with Alf thanks to the awesome
18
+ [Sequel](http://sequel.rubyforge.org/) library.
19
+
20
+ ## Example
21
+
22
+ ```ruby
23
+ require 'alf-sequel'
24
+
25
+ Alf.connect("sap.db") do |conn|
26
+
27
+ # Send a query and puts the result
28
+ # (See Alf main docs for smarter ways of using query results)
29
+ relation = conn.query{
30
+ restrict(suppliers, city: 'London')
31
+ }
32
+
33
+ puts relation
34
+ # => +------+-------+---------+--------+
35
+ # | :sid | :name | :status | :city |
36
+ # +------+-------+---------+--------+
37
+ # | S1 | Smith | 20 | London |
38
+ # | S4 | Clark | 20 | London |
39
+ # +------+-------+---------+--------+
40
+
41
+ # alf-sequel overrides `Operand#to_sql` in such a way that the resulting
42
+ # SQL code is handled by Sequel itself and thus valid for the DBMS
43
+ # considered
44
+ sql = conn.parse{
45
+ restrict(suppliers, city: 'London')
46
+ }.to_sql
47
+
48
+ puts sql
49
+ # => SELECT `t1`.`sid`, `t1`.`name`, `t1`.`status`, `t1`.`city`
50
+ # FROM `suppliers` AS 't1'
51
+ # WHERE (`t1`.`city` = 'London')
52
+ end
53
+ ```
data/lib/alf/sequel.rb CHANGED
@@ -2,6 +2,7 @@ require_relative 'sequel/version'
2
2
  require_relative 'sequel/loader'
3
3
  require_relative 'sequel/adapter'
4
4
  require_relative 'sequel/connection'
5
+ require_relative 'sequel/translator'
5
6
  require_relative 'sequel/compiler'
6
7
  require_relative 'sequel/cog'
7
8
  require_relative 'sequel/unit_of_work'
@@ -1,97 +1,45 @@
1
1
  module Alf
2
2
  module Sequel
3
3
  class Cog
4
- include Engine::Cog
4
+ include Alf::Compiler::Cog
5
+ include Enumerable
5
6
 
6
- def initialize(expr, connection, opts)
7
- super(expr)
8
- @connection = connection
9
- @opts = opts
7
+ def initialize(expr, compiler, sexpr)
8
+ super(expr, compiler)
9
+ @sexpr = sexpr
10
10
  end
11
- attr_reader :connection, :opts
11
+ attr_reader :sexpr
12
12
 
13
- def as; opts[:as]; end
14
- def dataset; opts[:dataset]; end
15
-
16
- ### Cog
17
-
18
- def to_relation
19
- Relation.coerce(to_a)
20
- end
21
-
22
- def each
23
- dataset.each(&Proc.new)
24
- end
25
-
26
- ### Delegation to Dataset, that is, facade over ::Sequel itself
27
-
28
- def select(expr, attrs)
29
- branch expr, dataset: dataset.select(*qualify(attrs))
30
- end
31
-
32
- def rename(expr, attrs, opts)
33
- branch expr, dataset: dataset.select(*qualify(attrs)).from_self(opts),
34
- as: opts[:alias]
35
- end
36
-
37
- def distinct(expr, *args, &bl)
38
- branch expr, dataset: dataset.distinct(*args, &bl)
13
+ def should_be_reused?
14
+ sexpr.should_be_reused?
39
15
  end
40
16
 
41
- def order(expr, *args, &bl)
42
- branch expr, dataset: dataset.order(*args, &bl)
17
+ def cog_orders
18
+ [ sexpr.ordering ].compact
43
19
  end
44
20
 
45
- def filter(expr, *args, &bl)
46
- branch expr, dataset: dataset.filter(*args, &bl)
21
+ def dataset
22
+ @dataset ||= Translator.new(compiler.connection).call(sexpr)
47
23
  end
48
24
 
49
- def intersect(expr, other, opts={})
50
- branch expr, dataset: dataset.intersect(other.dataset, opts),
51
- as: opts[:alias]
25
+ def to_sql(buffer = "")
26
+ buffer << dataset.sql
27
+ buffer
52
28
  end
53
29
 
54
- def join(expr, other, cols, opts={})
55
- join = dataset.from_self.inner_join(other.dataset, cols, :table_alias => opts[:alias])
56
- branch expr, dataset: join.from_self(opts),
57
- as: opts[:alias]
58
- end
59
-
60
- def union(expr, other, opts={})
61
- branch expr, dataset: dataset.union(other.dataset, opts),
62
- as: opts[:alias]
63
- end
64
-
65
- def limit(expr, *args, &bl)
66
- branch expr, dataset: dataset.limit(*args, &bl)
67
- end
68
-
69
- ### compilation tools
70
-
71
- def sql
72
- dataset.sql
73
- end
74
-
75
- def qualify(attributes)
76
- return attributes unless as
77
- case attributes
78
- when Symbol
79
- ::Sequel.qualify(as, attributes)
80
- when Hash
81
- attributes.map{|k,v| ::Sequel.as(::Sequel.qualify(as, k), v) }
82
- else
83
- attributes.map{|a| ::Sequel.qualify(as, a)}
84
- end
30
+ def to_s
31
+ to_sql
85
32
  end
86
33
 
87
- def branch(expr, opts = {})
88
- Cog.new expr, connection, self.opts.merge(opts)
34
+ def inspect
35
+ "Alf::Sequel::Cog(#{to_sql})"
89
36
  end
90
37
 
91
- def to_s
92
- "Alf::Sequel::Cog|#{sql}"
38
+ def each(&bl)
39
+ return to_enum unless block_given?
40
+ dataset.each(&bl)
93
41
  end
94
42
 
95
- end # class Operand
43
+ end # class Cog
96
44
  end # module Sequel
97
45
  end # module Alf
@@ -1,209 +1,19 @@
1
1
  module Alf
2
2
  module Sequel
3
- class Compiler < Algebra::Compiler
3
+ class Compiler < Sql::Compiler
4
4
 
5
- def pass(expr)
6
- rewrite(expr)
5
+ def initialize(connection)
6
+ super()
7
+ @connection = connection
7
8
  end
8
- alias :on_missing :pass
9
+ attr_reader :connection
9
10
 
10
- def next_alias
11
- @as ||= 0
12
- :"t#{@as += 1}"
13
- end
14
-
15
- def on_leaf_operand(expr)
16
- if Algebra::Operand::Named===expr
17
- expr.connection.cog(expr.name, expr, :alias => next_alias)
18
- else
19
- expr.to_cog
20
- end
21
- end
22
-
23
- ### non relational
24
-
25
- alias :on_autonum :pass
26
- alias :on_coerce :pass
27
- alias :on_defaults :pass
28
- alias :on_generator :pass
29
-
30
- def on_clip(expr)
31
- rewrite(expr){|rw|
32
- rw.operand.select(expr, expr.stay_attributes)
33
- }
34
- end
35
-
36
- def on_compact(expr)
37
- rewrite(expr){|rw|
38
- rw.operand.distinct(expr)
39
- }
40
- end
41
-
42
- def on_sort(expr)
43
- rewrite(expr){|rw|
44
- operand = rw.operand
45
- ordering = to_sequel_ordering(operand, expr.ordering)
46
- operand.order(expr, *ordering)
47
- }
48
- end
49
-
50
- ### relational
51
-
52
- alias :on_extend :pass
53
-
54
- def on_frame(expr)
55
- rewrite(expr){|rw|
56
- offset, limit, ordering = expr.offset, expr.limit, expr.total_ordering
57
- operand = rw.operand
58
- ordering = to_sequel_ordering(operand, ordering)
59
- operand.order(expr, *ordering).limit(expr, limit, offset)
60
- }
61
- end
62
-
63
- alias :on_group :pass
64
- alias :on_infer_heading :pass
65
-
66
- def on_intersect(expr)
67
- rewrite(expr){|rw|
68
- rw.left.intersect(expr, rw.right, {:alias => next_alias})
69
- }
70
- end
71
-
72
- def on_join(expr)
73
- rewrite(expr){|rw|
74
- rw.left.join(expr, rw.right, expr.common_attributes.to_a, {:alias => next_alias})
75
- }
76
- end
77
-
78
- def on_matching(expr)
79
- rewrite(expr) do |rw|
80
- rw.left.filter(expr, matching2filter(expr, rw))
81
- end
82
- end
83
-
84
- def on_not_matching(expr)
85
- rewrite(expr) do |rw|
86
- rw.left.filter(expr, ~matching2filter(expr, rw))
87
- end
88
- end
89
-
90
- def matching2filter(expr, rw)
91
- commons = expr.common_attributes.to_a
92
- if commons.size==1
93
- # (NOT) IN (SELECT ...)
94
- pred = ::Alf::Predicate.in(commons.first, rw.right.select(expr, commons).dataset)
95
- Predicate.new(:qualifier => rw.left.as).call(pred)
96
- elsif commons.size==0
97
- # (NOT) EXISTS (SELECT ... no join condition ...)
98
- rw.right.dataset.exists
99
- else
100
- # (NOT) EXISTS (SELECT ...)
101
- filter = Hash[rw.left.qualify(commons).zip(rw.right.qualify(commons))]
102
- filter = ::Sequel.expr filter
103
- filter = rw.right.filter(expr, filter)
104
- filter.dataset.exists
105
- end
106
- end
107
-
108
- alias :on_minus :pass
109
-
110
- def on_page(expr)
111
- rewrite(expr){|rw|
112
- index, size, ordering = expr.page_index, expr.page_size, expr.total_ordering
113
- operand, offset, limit = rw.operand, (index.abs - 1) * size, size
114
- ordering = to_sequel_ordering(operand, index >= 0 ? ordering : ordering.reverse)
115
- operand.order(expr, *ordering).limit(expr, offset , limit)
116
- }
117
- end
118
-
119
- def on_project(expr)
120
- rewrite(expr){|rw|
121
- compiled = rw.operand.select(expr, expr.stay_attributes)
122
- compiled = compiled.distinct(expr) unless key_preserving?(expr){ false }
123
- compiled
124
- }
125
- end
126
-
127
- alias :on_quota :pass
128
- alias :on_rank :pass
129
-
130
- def on_rename(expr)
131
- rewrite(expr){|rw|
132
- rw.operand.rename(expr, expr.complete_renaming.to_hash, :alias => next_alias)
133
- }
134
- end
135
-
136
- def on_restrict(expr)
137
- rewrite(expr){|rw|
138
- filter = Predicate.new(:qualifier => rw.operand.as).call(rw.predicate)
139
- rw.operand.filter(expr, filter)
140
- }
141
- end
142
-
143
- alias :on_summarize :pass
144
- alias :on_ungroup :pass
145
-
146
- def on_union(expr)
147
- rewrite(expr){|rw|
148
- rw.left.union(expr, rw.right, :alias => next_alias)
149
- }
150
- end
151
-
152
- alias :on_unwrap :pass
153
- alias :on_wrap :pass
154
-
155
- private
156
-
157
- def to_sequel_ordering(operand, ordering)
158
- ordering.to_a.map{|(sel,dir)|
159
- raise NotSupportedError if sel.composite?
160
- ::Sequel.send(dir, operand.qualify(sel.outcoerce))
161
- }
162
- end
163
-
164
- def key_preserving?(expr)
165
- expr.key_preserving?
166
- rescue NotSupportedError
167
- block_given? ? yield : raise
168
- end
169
-
170
- private
171
-
172
- def rewrite(expr)
173
- # copy and apply on expr. the result is the same expression, but
174
- # with cogs instead of operands
175
- rewritten = copy_and_apply(expr)
176
-
177
- # Continue compilation process provided all operands are recognized.
178
- if block_given? and rewritten.operands.all?{|op| recognized?(op) }
179
- # Compilation of predicates may throw a pass
180
- catch(:pass){
181
- rewritten = yield(rewritten)
182
- }
183
- end
184
-
185
- # If the result is itself recognized, proceed
186
- # Otherwise, give a change to the upper-stage compiler with a proxy
187
- if recognized?(rewritten)
188
- rewritten
189
- else
190
- operands = rewritten.operands.map{|op|
191
- Algebra::Operand::Proxy.new(op)
192
- }
193
- rewritten = rewritten.with_operands(*operands)
194
- engine.call(rewritten)
195
- end
196
- end
197
-
198
- def recognized?(op)
199
- op.is_a?(Cog)
200
- end
11
+ protected
201
12
 
202
- def engine
203
- @engine ||= Engine::Compiler.new
13
+ def cog_class
14
+ Cog
204
15
  end
205
16
 
206
17
  end # class Compiler
207
18
  end # module Sequel
208
19
  end # module Alf
209
- require_relative 'compiler/predicate'