alf-sequel 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -6,7 +6,11 @@ module Alf
6
6
  class Connection < Alf::Adapter::Connection
7
7
 
8
8
  def compiler
9
- Compiler.new
9
+ @compiler ||= Compiler.new(self)
10
+ end
11
+
12
+ def translator
13
+ @translator ||= Translator.new(self)
10
14
  end
11
15
 
12
16
  require_relative 'connection/schema_methods'
@@ -28,12 +28,14 @@ module Alf
28
28
  @sequel_db.disconnect if @sequel_db && @sequel_db != conn_spec
29
29
  end
30
30
 
31
+ def close!
32
+ @sequel_db.disconnect if @sequel_db
33
+ end
34
+
31
35
  def with_sequel_db
32
36
  yield(sequel_db)
33
37
  end
34
38
 
35
- private
36
-
37
39
  # Yields a Sequel::Database object
38
40
  def sequel_db
39
41
  @sequel_db ||= Adapter.sequel_db(conn_spec)
@@ -11,12 +11,8 @@ module Alf
11
11
  sequel_db[name]
12
12
  end
13
13
 
14
- def cog(name, expr = nil, opts = {})
15
- if as = opts[:alias]
16
- Cog.new(expr, self, dataset: dataset(:"#{name}___#{as}"), as: as)
17
- else
18
- Cog.new(expr, self, dataset: dataset(name))
19
- end
14
+ def cog(plan, expr)
15
+ plan.join(compiler).on_leaf_operand(plan, expr)
20
16
  end
21
17
 
22
18
  def heading(name)
@@ -38,7 +38,7 @@ module Alf
38
38
  def with_dataset(name, predicate = nil)
39
39
  ds = name.is_a?(Symbol) ? sequel_db[name] : name
40
40
  if predicate && !predicate.tautology?
41
- ds = ds.filter(Compiler::Predicate.call(predicate))
41
+ ds = ds.where(Translator.new(sequel_db).call(predicate.sexpr))
42
42
  end
43
43
  yield(ds) if block_given?
44
44
  end
@@ -1,2 +1,3 @@
1
1
  require "alf-core"
2
+ require "alf-sql"
2
3
  require "sequel"
@@ -0,0 +1,198 @@
1
+ module Alf
2
+ module Sequel
3
+ class Translator < Sexpr::Processor
4
+
5
+ def initialize(connection)
6
+ @connection = connection
7
+ end
8
+ attr_reader :connection
9
+
10
+ def on_with_exp(sexpr)
11
+ if sequel_db.select(1).supports_cte?
12
+ dataset = apply(sexpr.select_exp)
13
+ apply(sexpr.with_spec).each_pair do |name,subquery|
14
+ dataset = dataset.with(name, subquery)
15
+ end
16
+ dataset
17
+ else
18
+ apply(Sql::Processor::Flatten.new(Sql::Builder.new).call(sexpr))
19
+ end
20
+ end
21
+
22
+ def on_with_spec(sexpr)
23
+ sexpr.each_with_object({}){|child,hash|
24
+ next if child == :with_spec
25
+ hash[apply(child.table_name)] = apply(child.subquery)
26
+ }
27
+ end
28
+
29
+ def on_set_operator(sexpr)
30
+ left, right = apply(sexpr.left), apply(sexpr.right)
31
+ left.send(sexpr.first, right, all: sexpr.all?, from_self: false)
32
+ end
33
+ alias :on_union :on_set_operator
34
+ alias :on_intersect :on_set_operator
35
+ alias :on_except :on_set_operator
36
+
37
+ def on_select_exp(sexpr)
38
+ dataset = sequel_db.select(1)
39
+ dataset = dataset(apply(sexpr.from_clause)) if sexpr.from_clause
40
+ #
41
+ selection = apply(sexpr.select_list)
42
+ predicate = apply(sexpr.predicate) if sexpr.predicate
43
+ order = apply(sexpr.order_by_clause) if sexpr.order_by_clause
44
+ limit = apply(sexpr.limit_clause) if sexpr.limit_clause
45
+ offset = apply(sexpr.offset_clause) if sexpr.offset_clause
46
+ #
47
+ dataset = dataset.select(*selection)
48
+ dataset = dataset.distinct if sexpr.distinct?
49
+ dataset = dataset.where(predicate) if predicate
50
+ dataset = dataset.order_by(*order) if order
51
+ dataset = dataset.limit(limit, offset) if limit or offset
52
+ dataset
53
+ end
54
+
55
+ def on_select_list(sexpr)
56
+ sexpr.sexpr_body.map{|c| apply(c) }
57
+ end
58
+
59
+ def on_select_star(sexpr)
60
+ ::Sequel.lit('*')
61
+ end
62
+
63
+ def on_select_item(sexpr)
64
+ ::Sequel.as(apply(sexpr.left), apply(sexpr.right))
65
+ end
66
+
67
+ def on_qualified_name(sexpr)
68
+ apply(sexpr.last).qualify(sexpr.qualifier)
69
+ end
70
+
71
+ def on_column_name(sexpr)
72
+ ::Sequel.expr(sexpr.last.to_sym)
73
+ end
74
+
75
+ def on_from_clause(sexpr)
76
+ apply(sexpr.table_spec)
77
+ end
78
+
79
+ def on_table_name(sexpr)
80
+ ::Sequel.identifier(sexpr.last)
81
+ end
82
+
83
+ def on_cross_join(sexpr)
84
+ left, right = apply(sexpr.left), apply(sexpr.right)
85
+ dataset(left).cross_join(right)
86
+ end
87
+
88
+ def on_inner_join(sexpr)
89
+ left, right = apply(sexpr.left), apply(sexpr.right)
90
+ options = {qualify: false, table_alias: false}
91
+ dataset(left).join_table(:inner, right, nil, options){|*args|
92
+ apply(sexpr.predicate)
93
+ }
94
+ end
95
+
96
+ def on_table_as(sexpr)
97
+ ::Sequel.as(sexpr.table_name.to_sym, sexpr.as_name)
98
+ end
99
+
100
+ def on_subquery_as(sexpr)
101
+ ::Sequel.as(apply(sexpr.subquery), sexpr.as_name)
102
+ end
103
+
104
+ def on_order_by_clause(sexpr)
105
+ sexpr.sexpr_body.map{|c| apply(c)}
106
+ end
107
+
108
+ def on_order_by_term(sexpr)
109
+ ::Sequel.send(sexpr.direction, apply(sexpr.qualified_name))
110
+ end
111
+
112
+ def on_limit_clause(sexpr)
113
+ sexpr.last
114
+ end
115
+
116
+ def on_offset_clause(sexpr)
117
+ sexpr.last
118
+ end
119
+
120
+ ### Predicate
121
+
122
+ def on_identifier(sexpr)
123
+ ::Sequel.identifier(sexpr.last)
124
+ end
125
+
126
+ def on_qualified_identifier(sexpr)
127
+ ::Sequel.as(sexpr.qualifier, sexpr.name)
128
+ end
129
+
130
+ def on_tautology(sexpr)
131
+ ::Sequel::SQL::BooleanConstant.new(true)
132
+ end
133
+
134
+ def on_contradiction(sexpr)
135
+ ::Sequel::SQL::BooleanConstant.new(false)
136
+ end
137
+
138
+ def on_literal(sexpr)
139
+ sexpr.last.nil? ? nil : ::Sequel.expr(sexpr.last)
140
+ end
141
+
142
+ def on_eq(sexpr)
143
+ left, right = apply(sexpr.left), apply(sexpr.right)
144
+ ::Sequel.expr(left => right)
145
+ end
146
+
147
+ def on_neq(sexpr)
148
+ left, right = apply(sexpr.left), apply(sexpr.right)
149
+ ~::Sequel.expr(left => right)
150
+ end
151
+
152
+ def on_dyadic_comp(sexpr)
153
+ left, right = apply(sexpr.left), apply(sexpr.right)
154
+ left.send(sexpr.operator_symbol, right)
155
+ end
156
+ alias :on_lt :on_dyadic_comp
157
+ alias :on_lte :on_dyadic_comp
158
+ alias :on_gt :on_dyadic_comp
159
+ alias :on_gte :on_dyadic_comp
160
+
161
+ def on_in(sexpr)
162
+ left, right = apply(sexpr.identifier), sexpr.last
163
+ right = apply(right) if sexpr.subquery?
164
+ ::Sequel.expr(left => right)
165
+ end
166
+
167
+ def on_exists(sexpr)
168
+ apply(sexpr.last).exists
169
+ end
170
+
171
+ def on_not(sexpr)
172
+ ~apply(sexpr.last)
173
+ end
174
+
175
+ def on_and(sexpr)
176
+ body = sexpr.sexpr_body
177
+ body[1..-1].inject(apply(body.first)){|f,t| f & apply(t) }
178
+ end
179
+
180
+ def on_or(sexpr)
181
+ body = sexpr.sexpr_body
182
+ body[1..-1].inject(apply(body.first)){|f,t| f | apply(t) }
183
+ end
184
+
185
+ private
186
+
187
+ def sequel_db
188
+ connection.sequel_db
189
+ end
190
+
191
+ def dataset(expr)
192
+ return expr if ::Sequel::Dataset===expr
193
+ sequel_db[expr]
194
+ end
195
+
196
+ end # class Translator
197
+ end # module Sequel
198
+ end # module Alf
@@ -39,13 +39,19 @@ module Alf
39
39
  def _run
40
40
  connection.with_dataset(@relvar_name) do |d|
41
41
  bk = best_candidate_key
42
- if bk and bk.size == 1
42
+ supported = d.supports_returning?(:insert)
43
+
44
+ if supported and bk and bk.size == 1
43
45
  pk_field_name = bk.to_a.first
44
- supported = d.supports_returning?(:insert)
45
46
  d = d.returning(pk_field_name) if supported
46
47
  @insert_result = @inserted.map{|t|
47
- res = d.insert(t.to_hash)
48
- supported ? res.first : { pk_field_name => res }
48
+ d.insert(t.to_hash).first
49
+ }
50
+ elsif bk
51
+ pk_field_name = bk.to_a.first
52
+ @insert_result = @inserted.map{|t|
53
+ result = d.insert(t.to_hash)
54
+ bk.project_tuple(t) || { pk_field_name => result }
49
55
  }
50
56
  else
51
57
  @inserted.each{|t| d.insert(t.to_hash) }
@@ -39,9 +39,9 @@ module Alf
39
39
  mr
40
40
  else
41
41
  filter = mr.tuple_extract.to_hash
42
- tuples = connection.cog(@relvar_name)
43
- .filter(nil, filter)
44
- .select(nil, pkey.to_a)
42
+ tuples = connection.dataset(@relvar_name)
43
+ .filter(filter)
44
+ .select(pkey.to_a)
45
45
  Relation(tuples)
46
46
  end
47
47
  end
@@ -3,7 +3,7 @@ module Alf
3
3
  module Version
4
4
 
5
5
  MAJOR = 0
6
- MINOR = 14
6
+ MINOR = 15
7
7
  TINY = 0
8
8
 
9
9
  def self.to_s
@@ -6,7 +6,7 @@ module Alf
6
6
  subject{ sap.heading(:suppliers) }
7
7
 
8
8
  let(:expected){
9
- Heading[:sid => Integer, :name => String, :status => Integer, :city => String]
9
+ Heading[:sid => String, :name => String, :status => Integer, :city => String]
10
10
  }
11
11
 
12
12
  it{ should eq(expected) }
Binary file
data/spec/spec_helper.rb CHANGED
@@ -2,12 +2,12 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
2
  require 'alf-sequel'
3
3
  require "rspec"
4
4
  require 'path'
5
- require_relative 'fixtures/sap.rb'
5
+ require 'logger'
6
6
 
7
7
  module Helpers
8
8
 
9
9
  def sequel_database_path
10
- Path.dir/'alf.db'
10
+ Path.dir/'sap.db'
11
11
  end
12
12
 
13
13
  def sequel_database_uri
@@ -15,15 +15,58 @@ module Helpers
15
15
  end
16
16
 
17
17
  def sequel_database_memory
18
- "#{Alf::Sequel::Adapter.sqlite_protocol}::memory:"
18
+ "#{Alf::Sequel::Adapter.sqlite_protocol}:memory"
19
19
  end
20
20
 
21
21
  def sap
22
- @sap ||= Alf.connect Path.relative("fixtures/sap.db")
22
+ sequel_db = ::Sequel.connect(sequel_database_uri)
23
+ Alf.connect(sequel_db, schema_cache: false)
23
24
  end
24
25
 
25
26
  def sap_memory
26
- Alf.connect(SAP.create!(sequel_database_memory), schema_cache: false)
27
+ sequel_db = ::Sequel.connect(sequel_database_memory)
28
+ sequel_db.create_table(:suppliers) do
29
+ String :sid
30
+ String :name
31
+ Integer :status
32
+ String :city
33
+ primary_key [:sid]
34
+ index :name, :unique => true
35
+ end
36
+ sequel_db.create_table(:parts) do
37
+ String :pid
38
+ String :name
39
+ String :color
40
+ Float :weight
41
+ String :city
42
+ primary_key [:pid]
43
+ end
44
+ sequel_db.create_table(:supplies) do
45
+ foreign_key :sid, :suppliers, :null=>false, :key=>[:sid], :deferrable=>true
46
+ foreign_key :pid, :parts, :null=>false, :key=>[:pid], :deferrable=>true
47
+ Integer :qty
48
+ primary_key [:sid, :pid]
49
+ end
50
+ [
51
+ {:sid => 'S1', :name => 'Smith', :status => 20, :city => 'London'},
52
+ {:sid => 'S2', :name => 'Jones', :status => 10, :city => 'Paris'},
53
+ {:sid => 'S3', :name => 'Blake', :status => 30, :city => 'Paris'},
54
+ {:sid => 'S4', :name => 'Clark', :status => 20, :city => 'London'},
55
+ {:sid => 'S5', :name => 'Adams', :status => 30, :city => 'Athens'}
56
+ ].each do |tuple|
57
+ sequel_db[:suppliers].insert(tuple)
58
+ end
59
+ [
60
+ {:pid => 'P1', :name => 'Nut', :color => 'Red', :weight => 12.0, :city => 'London'},
61
+ {:pid => 'P2', :name => 'Bolt', :color => 'Green', :weight => 17.0, :city => 'Paris'},
62
+ {:pid => 'P3', :name => 'Screw', :color => 'Blue', :weight => 17.0, :city => 'Oslo'},
63
+ {:pid => 'P4', :name => 'Screw', :color => 'Red', :weight => 14.0, :city => 'London'},
64
+ {:pid => 'P5', :name => 'Cam', :color => 'Blue', :weight => 12.0, :city => 'Paris'},
65
+ {:pid => 'P6', :name => 'Cog', :color => 'Red', :weight => 19.0, :city => 'London'}
66
+ ].each do |tuple|
67
+ sequel_db[:parts].insert(tuple)
68
+ end
69
+ Alf.connect(sequel_db, schema_cache: false)
27
70
  end
28
71
 
29
72
  end
@@ -31,5 +74,4 @@ end
31
74
  RSpec.configure do |c|
32
75
  c.include Helpers
33
76
  c.extend Helpers
34
- c.filter_run_excluding :ruby19 => (RUBY_VERSION < "1.9")
35
77
  end
@@ -25,11 +25,11 @@ module Alf
25
25
 
26
26
  context 'when predicate is not a tautology' do
27
27
  let(:relvar_name){ :suppliers }
28
- let(:predicate){ Predicate.eq(sid: 1) }
28
+ let(:predicate){ Predicate.eq(sid: "S1") }
29
29
 
30
30
  it 'removes only targetted tuples' do
31
31
  conn.dataset(relvar_name).should_not be_empty
32
- conn.dataset(relvar_name).where(sid: 1).should be_empty
32
+ conn.dataset(relvar_name).where(sid: "S1").should be_empty
33
33
  end
34
34
  end
35
35
 
@@ -18,30 +18,30 @@ module Alf
18
18
  let(:relvar_name){ :suppliers }
19
19
 
20
20
  context 'with only one tuple' do
21
- let(:tuples){ [{sid: 10, name: "Marcus", city: "Ouagadougou", status: 55}] }
21
+ let(:tuples){ [{sid: 'S10', name: "Marcus", city: "Ouagadougou", status: 55}] }
22
22
 
23
23
  it "inserts the tuple" do
24
- conn.dataset(:suppliers).where(sid: 10).should_not be_empty
24
+ conn.dataset(:suppliers).where(sid: "S10").should_not be_empty
25
25
  end
26
26
 
27
27
  it 'keeps information about inserted tuples' do
28
- uow.matching_relation.should eq(Relation sid: 10)
28
+ uow.matching_relation.should eq(Relation sid: 'S10')
29
29
  end
30
30
  end
31
31
 
32
32
  context 'with multiple tuples' do
33
33
  let(:tuples){ [
34
- {sid: 10, name: "Marcus", city: "Ouagadougou", status: 55},
35
- {sid: 11, name: "Demete", city: "Albertville", status: 56}
34
+ {sid: "S10", name: "Marcus", city: "Ouagadougou", status: 55},
35
+ {sid: "S11", name: "Demete", city: "Albertville", status: 56}
36
36
  ]}
37
37
 
38
38
  it "inserts the tuples" do
39
- conn.dataset(:suppliers).where(sid: 10).should_not be_empty
40
- conn.dataset(:suppliers).where(sid: 11).should_not be_empty
39
+ conn.dataset(:suppliers).where(sid: "S10").should_not be_empty
40
+ conn.dataset(:suppliers).where(sid: "S11").should_not be_empty
41
41
  end
42
42
 
43
43
  it 'keeps information about inserted tuples' do
44
- uow.matching_relation.should eq(Relation sid: [10, 11])
44
+ uow.matching_relation.should eq(Relation sid: ["S10", "S11"])
45
45
  end
46
46
  end
47
47
  end
@@ -50,18 +50,16 @@ module Alf
50
50
  let(:relvar_name){ :supplies }
51
51
 
52
52
  let(:tuples){ [
53
- {sid: 5, pid: 1},
54
- {sid: 5, pid: 2}
53
+ {sid: "S5", pid: "P1"},
54
+ {sid: "S5", pid: "P2"}
55
55
  ]}
56
56
 
57
57
  it "inserts the tuples" do
58
- conn.dataset(:supplies).where(sid: 5).to_a.size.should eq(2)
58
+ conn.dataset(:supplies).where(sid: "S5").to_a.size.should eq(2)
59
59
  end
60
60
 
61
61
  it 'keeps information about inserted tuples' do
62
- pending{
63
- uow.matching_relation.should eq(Relation(tuples))
64
- }
62
+ uow.matching_relation.should eq(Relation(tuples))
65
63
  end
66
64
  end
67
65