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
@@ -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