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.
- data/Gemfile +10 -7
- data/Gemfile.lock +26 -14
- data/README.md +45 -3
- data/lib/alf/sequel.rb +1 -0
- data/lib/alf/sequel/cog.rb +23 -75
- data/lib/alf/sequel/compiler.rb +8 -198
- data/lib/alf/sequel/connection.rb +5 -1
- data/lib/alf/sequel/connection/connection_methods.rb +4 -2
- data/lib/alf/sequel/connection/schema_methods.rb +2 -6
- data/lib/alf/sequel/connection/update_methods.rb +1 -1
- data/lib/alf/sequel/loader.rb +1 -0
- data/lib/alf/sequel/translator.rb +198 -0
- data/lib/alf/sequel/unit_of_work/insert.rb +10 -4
- data/lib/alf/sequel/unit_of_work/update.rb +3 -3
- data/lib/alf/sequel/version.rb +1 -1
- data/spec/connection/test_heading.rb +1 -1
- data/spec/{fixtures/sap.db → sap.db} +0 -0
- data/spec/spec_helper.rb +48 -6
- data/spec/unit_of_work/delete/test_delete.rb +2 -2
- data/spec/unit_of_work/insert/test_run.rb +12 -14
- data/spec/unit_of_work/update/test_run.rb +1 -1
- data/tasks/bench.rake +40 -0
- data/tasks/test.rake +3 -3
- metadata +29 -29
- data/lib/alf/sequel/compiler/predicate.rb +0 -76
- data/spec/alf.db +0 -0
- data/spec/compiler/test_clip.rb +0 -40
- data/spec/compiler/test_compact.rb +0 -18
- data/spec/compiler/test_extend.rb +0 -16
- data/spec/compiler/test_frame.rb +0 -89
- data/spec/compiler/test_intersect.rb +0 -18
- data/spec/compiler/test_join.rb +0 -26
- data/spec/compiler/test_leaf_operand.rb +0 -24
- data/spec/compiler/test_matching.rb +0 -34
- data/spec/compiler/test_not_matching.rb +0 -34
- data/spec/compiler/test_page.rb +0 -86
- data/spec/compiler/test_predicate.rb +0 -141
- data/spec/compiler/test_project.rb +0 -64
- data/spec/compiler/test_rename.rb +0 -26
- data/spec/compiler/test_restrict.rb +0 -48
- data/spec/compiler/test_sort.rb +0 -18
- data/spec/compiler/test_union.rb +0 -18
- data/spec/compiler_helper.rb +0 -34
- data/spec/fixtures/sap.rb +0 -43
- data/tasks/fixtures.rake +0 -12
@@ -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(
|
15
|
-
|
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.
|
41
|
+
ds = ds.where(Translator.new(sequel_db).call(predicate.sexpr))
|
42
42
|
end
|
43
43
|
yield(ds) if block_given?
|
44
44
|
end
|
data/lib/alf/sequel/loader.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
48
|
-
|
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.
|
43
|
-
.filter(
|
44
|
-
.select(
|
42
|
+
tuples = connection.dataset(@relvar_name)
|
43
|
+
.filter(filter)
|
44
|
+
.select(pkey.to_a)
|
45
45
|
Relation(tuples)
|
46
46
|
end
|
47
47
|
end
|
data/lib/alf/sequel/version.rb
CHANGED
@@ -6,7 +6,7 @@ module Alf
|
|
6
6
|
subject{ sap.heading(:suppliers) }
|
7
7
|
|
8
8
|
let(:expected){
|
9
|
-
Heading[:sid =>
|
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
|
-
|
5
|
+
require 'logger'
|
6
6
|
|
7
7
|
module Helpers
|
8
8
|
|
9
9
|
def sequel_database_path
|
10
|
-
Path.dir/'
|
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}
|
18
|
+
"#{Alf::Sequel::Adapter.sqlite_protocol}:memory"
|
19
19
|
end
|
20
20
|
|
21
21
|
def sap
|
22
|
-
|
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
|
-
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
35
|
-
{sid:
|
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:
|
40
|
-
conn.dataset(:suppliers).where(sid:
|
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: [
|
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:
|
54
|
-
{sid:
|
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:
|
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
|
-
|
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
|
|