alf 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 (67) hide show
  1. data/Gemfile +6 -3
  2. data/Gemfile.lock +65 -19
  3. data/README.md +77 -183
  4. data/Rakefile +25 -0
  5. data/alf.gemspec +5 -3
  6. data/alf.noespec +8 -6
  7. data/lib/alf/loader.rb +1 -0
  8. data/lib/alf/version.rb +1 -1
  9. data/spec/facade/test_query.rb +12 -0
  10. data/spec/key-inference/queries.yml +10 -0
  11. data/spec/key-inference/test_all.rb +21 -0
  12. data/{test → spec}/migrations/test_folder_migration.rb +0 -0
  13. data/{test → spec}/migrations/test_sequel_migration.rb +1 -1
  14. data/spec/operators/ungroup/grouped.json +3 -0
  15. data/spec/operators/ungroup/test_on_json_content.rb +11 -0
  16. data/spec/operators/unwrap/test_on_json_content.rb +11 -0
  17. data/spec/operators/unwrap/wrapped.json +3 -0
  18. data/spec/optimizer/project/extend.yml +20 -0
  19. data/spec/optimizer/project/intersect.yml +10 -0
  20. data/spec/optimizer/project/join.yml +20 -0
  21. data/spec/optimizer/project/matching.yml +21 -0
  22. data/spec/optimizer/project/minus.yml +9 -0
  23. data/spec/optimizer/project/not_matching.yml +21 -0
  24. data/spec/optimizer/project/project.yml +88 -0
  25. data/spec/optimizer/project/rename.yml +30 -0
  26. data/spec/optimizer/project/sort.yml +45 -0
  27. data/spec/optimizer/project/union.yml +8 -0
  28. data/spec/optimizer/restrict/clip.yml +4 -0
  29. data/spec/optimizer/restrict/compact.yml +4 -0
  30. data/spec/optimizer/restrict/generator.yml +4 -0
  31. data/spec/optimizer/restrict/intersect.yml +4 -0
  32. data/spec/optimizer/restrict/leaf_operand.yml +4 -0
  33. data/spec/optimizer/restrict/minus.yml +4 -0
  34. data/spec/optimizer/restrict/page.yml +12 -0
  35. data/spec/optimizer/restrict/project.yml +4 -0
  36. data/spec/optimizer/restrict/sort.yml +4 -0
  37. data/spec/optimizer/restrict/union.yml +4 -0
  38. data/spec/optimizer/test_all.rb +34 -0
  39. data/spec/sql/helpers.rb +25 -0
  40. data/spec/sql/queries/01-leaf-operand.yml +5 -0
  41. data/spec/sql/queries/02-clip.yml +12 -0
  42. data/spec/sql/queries/03-sort.yml +58 -0
  43. data/spec/sql/queries/04-frame.yml +57 -0
  44. data/spec/sql/queries/05-intersect.yml +23 -0
  45. data/spec/sql/queries/06-join.yml +207 -0
  46. data/spec/sql/queries/07-matching.yml +76 -0
  47. data/spec/sql/queries/08-minus.yml +23 -0
  48. data/spec/sql/queries/09-not-matching.yml +57 -0
  49. data/spec/sql/queries/10-page.yml +31 -0
  50. data/spec/sql/queries/11-project.yml +48 -0
  51. data/spec/sql/queries/12-rename.yml +24 -0
  52. data/spec/sql/queries/13-restrict.yml +114 -0
  53. data/spec/sql/queries/15-union.yml +90 -0
  54. data/spec/sql/queries/16-wrap.yml +3 -0
  55. data/spec/sql/queries/91-reuse.yml +28 -0
  56. data/spec/sql/test_sequel_compiler.rb +41 -0
  57. data/spec/sql/test_sql_compiler.rb +52 -0
  58. data/{test → spec}/test_alf.rb +0 -0
  59. data/spec/test_helpers.rb +54 -0
  60. data/tasks/doc.rake +10 -0
  61. data/tasks/fixtures.rake +52 -0
  62. data/tasks/mod.rake +50 -0
  63. data/tasks/release.rake +34 -0
  64. data/tasks/test.rake +2 -2
  65. metadata +150 -19
  66. data/test/seeding/test_seeding.rb +0 -49
  67. data/test/test_helpers.rb +0 -24
data/lib/alf/loader.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require "alf-core"
2
+ require "alf-sql"
2
3
  require "alf-sequel"
3
4
  require "alf-shell"
data/lib/alf/version.rb CHANGED
@@ -2,7 +2,7 @@ module Alf
2
2
  module Version
3
3
 
4
4
  MAJOR = 0
5
- MINOR = 14
5
+ MINOR = 15
6
6
  TINY = 0
7
7
 
8
8
  def self.to_s
@@ -0,0 +1,12 @@
1
+ require 'test_helpers'
2
+ describe Alf, "query" do
3
+
4
+ subject{ Alf.query(adapter){ suppliers } }
5
+
6
+ it{ should be_a(Relation) }
7
+
8
+ it 'should have expected heading' do
9
+ subject.heading.should eq(Heading(sid: String, name: String, status: Integer, city: String))
10
+ end
11
+
12
+ end
@@ -0,0 +1,10 @@
1
+ - alf: |-
2
+ suppliers
3
+ keys:
4
+ - [ sid ]
5
+ - [ name ]
6
+ - alf: |-
7
+ matching(suppliers, Relation(city: ['London', 'Paris']))
8
+ keys:
9
+ - [ sid ]
10
+ - [ name ]
@@ -0,0 +1,21 @@
1
+ require 'test_helpers'
2
+ module Alf
3
+ describe "Key inference" do
4
+
5
+ subject{ expr.keys }
6
+
7
+ (Path.dir/"queries.yml").load.each do |query|
8
+ alf = query['alf']
9
+ keys = query['keys']
10
+
11
+ describe "Key inference on '#{query}'" do
12
+ let(:expr){ conn.parse(alf) }
13
+
14
+ it 'should be as expected' do
15
+ subject.should eq(Alf::Keys.coerce(keys))
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
File without changes
@@ -2,7 +2,7 @@ require 'test_helpers'
2
2
  describe "Migrations with a Sequel adapter" do
3
3
 
4
4
  let(:db){
5
- Alf.database(victim.conn_spec, opts)
5
+ Alf.database("#{Alf::Sequel::Adapter.sqlite_protocol}:memory", opts)
6
6
  }
7
7
 
8
8
  context 'when no migration folder is set' do
@@ -0,0 +1,3 @@
1
+ [{
2
+ "suppliers": [ {"sid": "S1"} ]
3
+ }]
@@ -0,0 +1,11 @@
1
+ require 'test_helpers'
2
+ describe Alf::Algebra::Ungroup, "on json content" do
3
+
4
+ it 'works as expected' do
5
+ Alf.connect(Path.dir) do |conn|
6
+ rel = conn.relvar{ ungroup(grouped, :suppliers) }.to_a
7
+ rel.should eq([{sid: "S1"}])
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'test_helpers'
2
+ describe Alf::Algebra::Unwrap, "on json content" do
3
+
4
+ it 'works as expected' do
5
+ Alf.connect(Path.dir) do |conn|
6
+ rel = conn.relvar{ unwrap(wrapped, :wrapped) }.to_a
7
+ rel.should eq([{sid: "S1"}])
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,3 @@
1
+ [
2
+ { "wrapped": {"sid": "S1"} }
3
+ ]
@@ -0,0 +1,20 @@
1
+ # when extension only is kept
2
+ - alf: |-
3
+ project(extend(suppliers, a: "A", b: "B"), [:a, :b])
4
+ optimized: |-
5
+ project(extend(suppliers, a: "A", b: "B"), [:a, :b])
6
+ # when extension is not at all touched
7
+ - alf: |-
8
+ project(extend(suppliers, a: "A", b: "B"), [:sid, :a, :b])
9
+ optimized: |-
10
+ project(extend(suppliers, a: "A", b: "B"), [:sid, :a, :b])
11
+ # when extension is entirely removed
12
+ - alf: |-
13
+ project(extend(suppliers, a: "A", b: "B"), [:sid])
14
+ optimized: |-
15
+ project(suppliers, [:sid])
16
+ # when extension is partially removed
17
+ - alf: |-
18
+ project(extend(suppliers, a: "A", b: "B"), [:sid, :a])
19
+ optimized: |-
20
+ project(extend(suppliers, a: "A"), [:sid, :a])
@@ -0,0 +1,10 @@
1
+ - alf: |-
2
+ project(intersect(suppliers_in_london, suppliers_in_paris), [:name])
3
+ optimized: |-
4
+ project(intersect(suppliers_in_london, suppliers_in_paris), [:name])
5
+ comment: |-
6
+ This is NOT intersect(project, project). Indeed, no supplier is both in
7
+ Paris and in London, so the intersection is empty and hence the projection.
8
+ In contrast, two suppliers might have the same name and be one in London
9
+ and the other in Paris. Therefore, projecting first would return those
10
+ two names and the intersection would not be empty.
@@ -0,0 +1,20 @@
1
+ # join preserving all join attributes
2
+ - alf: |-
3
+ project(join(suppliers, supplies), [:sid, :name, :pid, :qty])
4
+ optimized: |-
5
+ join(project(suppliers, [:sid, :name]), supplies)
6
+ # join preserving all join attributes (allbut)
7
+ - alf: |-
8
+ allbut(join(suppliers, supplies), [:status, :qty])
9
+ optimized: |-
10
+ join(allbut(suppliers, [:status]), allbut(supplies, [:qty]))
11
+ # when not preserving all join attributes
12
+ - alf: |-
13
+ project(join(suppliers, supplies), [:name, :qty])
14
+ optimized: |-
15
+ project(join(project(suppliers, [:sid, :name]), project(supplies, [:sid, :qty])), [:name, :qty])
16
+ # when not preserving all join attributes (allbut)
17
+ - alf: |-
18
+ allbut(join(suppliers, supplies), [:sid, :pid])
19
+ optimized: |-
20
+ allbut(join(suppliers, allbut(supplies, [:pid])), [:sid])
@@ -0,0 +1,21 @@
1
+ # when matching attributes are preserved
2
+ - alf: |-
3
+ project(matching(suppliers, supplies), [:sid, :name])
4
+ optimized: |-
5
+ matching(project(suppliers, [:sid, :name]), supplies)
6
+ # when matching attributes are preserved (allbut)
7
+ - alf: |-
8
+ allbut(matching(suppliers, supplies), [:city])
9
+ optimized: |-
10
+ matching(allbut(suppliers, [:city]), supplies)
11
+
12
+ # when matching attributes are not preserved
13
+ - alf: |-
14
+ project(matching(suppliers, supplies), [:name])
15
+ optimized: |-
16
+ project(matching(project(suppliers, [:sid, :name]), supplies), [:name])
17
+ # when matching attributes are not preserved (allbut)
18
+ - alf: |-
19
+ allbut(matching(suppliers, supplies), [:sid, :name])
20
+ optimized: |-
21
+ allbut(matching(allbut(suppliers, [:name]), supplies), [:sid])
@@ -0,0 +1,9 @@
1
+ - alf: |-
2
+ project(minus(suppliers_in_london, suppliers_in_paris), [:name])
3
+ optimized: |-
4
+ project(minus(suppliers_in_london, suppliers_in_paris), [:name])
5
+ comment: |-
6
+ This is NOT minus(project, project). Indeed, as a supplier cannot be both
7
+ in Paris and in London, the minus simply returns the left operand. In
8
+ contrast, if we project first then the city information is lost and we
9
+ could restrict the left operand through common names, which is wrong.
@@ -0,0 +1,21 @@
1
+ # when matching attributes are preserved
2
+ - alf: |-
3
+ project(not_matching(suppliers, supplies), [:sid, :name])
4
+ optimized: |-
5
+ not_matching(project(suppliers, [:sid, :name]), supplies)
6
+ # when matching attributes are preserved (allbut)
7
+ - alf: |-
8
+ allbut(not_matching(suppliers, supplies), [:city])
9
+ optimized: |-
10
+ not_matching(allbut(suppliers, [:city]), supplies)
11
+
12
+ # when matching attributes are not preserved
13
+ - alf: |-
14
+ project(not_matching(suppliers, supplies), [:name])
15
+ optimized: |-
16
+ project(not_matching(project(suppliers, [:sid, :name]), supplies), [:name])
17
+ # when matching attributes are not preserved (allbut)
18
+ - alf: |-
19
+ allbut(not_matching(suppliers, supplies), [:sid, :name])
20
+ optimized: |-
21
+ allbut(not_matching(allbut(suppliers, [:name]), supplies), [:sid])
@@ -0,0 +1,88 @@
1
+ # projection on leaf operand
2
+ - alf: |-
3
+ project(suppliers, [:sid])
4
+ optimized: |-
5
+ project(suppliers, [:sid])
6
+
7
+ # allbut on leaf operand
8
+ - alf: |-
9
+ allbut(suppliers, [:sid])
10
+ optimized: |-
11
+ allbut(suppliers, [:sid])
12
+
13
+ # project(project) on different sets
14
+ - alf: |-
15
+ project(project(suppliers, [:sid]), [:name])
16
+ optimized: |-
17
+ project(suppliers, [])
18
+ # project(project) on a subset
19
+ - alf: |-
20
+ project(project(suppliers, [:sid, :name]), [:sid])
21
+ optimized: |-
22
+ project(suppliers, [:sid])
23
+ # project(project) on a superset
24
+ - alf: |-
25
+ project(project(suppliers, [:sid]), [:sid, :name])
26
+ optimized: |-
27
+ project(suppliers, [:sid])
28
+
29
+ # allbut(allbut) on different sets
30
+ - alf: |-
31
+ allbut(allbut(suppliers, [:sid]), [:name])
32
+ optimized: |-
33
+ allbut(suppliers, [:sid, :name])
34
+ # allbut(allbut) on a subset
35
+ - alf: |-
36
+ allbut(allbut(suppliers, [:sid, :name]), [:sid])
37
+ optimized: |-
38
+ allbut(suppliers, [:sid, :name])
39
+ # allbut(allbut) on a superset
40
+ - alf: |-
41
+ allbut(allbut(suppliers, [:sid]), [:sid, :name])
42
+ optimized: |-
43
+ allbut(suppliers, [:sid, :name])
44
+
45
+ # project(allbut(...)) on different sets
46
+ - alf: |-
47
+ project(allbut(suppliers, [:name]), [:sid])
48
+ optimized: |-
49
+ project(suppliers, [:sid])
50
+ # project(allbut(...)) on a subset
51
+ - alf: |-
52
+ project(allbut(suppliers, [:sid, :name]), [:sid])
53
+ optimized: |-
54
+ project(suppliers, [])
55
+ # project(allbut(...)) on a superset
56
+ - alf: |-
57
+ project(allbut(suppliers, [:sid]), [:sid, :name])
58
+ optimized: |-
59
+ project(suppliers, [:name])
60
+
61
+ # allbut(project(...)) on different sets
62
+ - alf: |-
63
+ allbut(project(suppliers, [:name]), [:sid])
64
+ optimized: |-
65
+ project(suppliers, [:name])
66
+ # allbut(project(...)) on a subset
67
+ - alf: |-
68
+ allbut(project(suppliers, [:sid, :name]), [:sid])
69
+ optimized: |-
70
+ project(suppliers, [:name])
71
+ # allbut(project(...)) on a superset
72
+ - alf: |-
73
+ allbut(project(suppliers, [:sid]), [:sid, :name])
74
+ optimized: |-
75
+ project(suppliers, [])
76
+
77
+ # project when used inside another otherator
78
+ - alf: |-
79
+ rename(project(project(suppliers, [:name, :sid]), [:sid]), sid: :id)
80
+ optimized: |-
81
+ rename(project(suppliers, [:sid]), sid: :id)
82
+ # project when should be applied recursively
83
+ - alf: |-
84
+ inside = project(project(suppliers, [:sid, :name, :status]), [:name, :status])
85
+ project(intersect(inside, suppliers), [:sname])
86
+ optimized: |-
87
+ inside = project(suppliers, [:name, :status])
88
+ project(intersect(inside, suppliers), [:sname])
@@ -0,0 +1,30 @@
1
+ # when renaming is preserved
2
+ - alf: |-
3
+ project(rename(suppliers, :sid => :id), [:id, :name])
4
+ optimized: |-
5
+ rename(project(suppliers, [:sid, :name]), :sid => :id)
6
+ # when renaming is preserved (allbut)
7
+ - alf: |-
8
+ allbut(rename(suppliers, :sid => :id), [:city])
9
+ optimized: |-
10
+ rename(allbut(suppliers, [:city]), :sid => :id)
11
+ # when renaming is projected away
12
+ - alf: |-
13
+ project(rename(suppliers, :sid => :id), [:name])
14
+ optimized: |-
15
+ project(suppliers, [:name])
16
+ # when renaming is partially projected away
17
+ - alf: |-
18
+ project(rename(suppliers, :sid => :id, :name => :sname), [:sname])
19
+ optimized: |-
20
+ rename(project(suppliers, [:name]), :name => :sname)
21
+ # when renaming is projected away (allbut)
22
+ - alf: |-
23
+ allbut(rename(suppliers, :sid => :id), [:id, :name])
24
+ optimized: |-
25
+ allbut(suppliers, [:sid, :name])
26
+ # when renaming is partially projected away (allbut)
27
+ - alf: |-
28
+ allbut(rename(suppliers, :sid => :id, :name => :sname), [:id])
29
+ optimized: |-
30
+ rename(allbut(suppliers, [:sid]), :name => :sname)
@@ -0,0 +1,45 @@
1
+ # when all sort attributes are preserved
2
+ - alf: |-
3
+ project(sort(suppliers, [:name, :asc]), [:sid, :name])
4
+ optimized: |-
5
+ sort(project(suppliers, [:sid, :name]), [:name, :asc])
6
+ # when all sort attributes are preserved (allbut)
7
+ - alf: |-
8
+ allbut(sort(suppliers, [:name, :asc]), [:city])
9
+ optimized: |-
10
+ sort(allbut(suppliers, [:city]), [:name, :asc])
11
+ # when no sort attribute is preserved
12
+ - alf: |-
13
+ project(sort(suppliers, [:name, :asc]), [:sid])
14
+ optimized: |-
15
+ project(sort(project(suppliers, [:sid, :name]), [:name, :asc]), [:sid])
16
+ # when no sort attribute is preserved (allbut)
17
+ - alf: |-
18
+ allbut(sort(suppliers, [:name, :asc]), [:name])
19
+ optimized: |-
20
+ allbut(sort(suppliers, [:name, :asc]), [:name])
21
+ # when no sort attribute is preserved (allbut II)
22
+ - alf: |-
23
+ allbut(sort(suppliers, [:name, :asc]), [:name, :city])
24
+ optimized: |-
25
+ allbut(sort(allbut(suppliers, [:city]), [:name, :asc]), [:name])
26
+ # when some sort attributes are preserved
27
+ - alf: |-
28
+ project(sort(suppliers, [[:name, :asc], [:sid, :asc]]), [:sid])
29
+ optimized: |-
30
+ project(sort(project(suppliers, [:sid, :name]), [[:name, :asc], [:sid, :asc]]), [:sid])
31
+ # when some sort attributes are preserved (II)
32
+ - alf: |-
33
+ project(sort(suppliers, [[:name, :asc], [:sid, :asc]]), [:sid, :city])
34
+ optimized: |-
35
+ project(sort(project(suppliers, [:sid, :name, :city]), [[:name, :asc], [:sid, :asc]]), [:sid, :city])
36
+ # when some sort attributes are preserved (allbut)
37
+ - alf: |-
38
+ allbut(sort(suppliers, [[:name, :asc], [:sid, :asc]]), [:sid])
39
+ optimized: |-
40
+ allbut(sort(suppliers, [[:name, :asc], [:sid, :asc]]), [:sid])
41
+ # when some sort attributes are preserved (allbut II)
42
+ - alf: |-
43
+ allbut(sort(suppliers, [[:name, :asc], [:sid, :asc]]), [:sid, :city])
44
+ optimized: |-
45
+ allbut(sort(allbut(suppliers, [:city]), [[:name, :asc], [:sid, :asc]]), [:sid])
@@ -0,0 +1,8 @@
1
+ - alf: |-
2
+ project(union(suppliers_in_london, suppliers_in_paris), [:name])
3
+ optimized: |-
4
+ union(project(suppliers_in_london, [:name]), project(suppliers_in_paris, [:name]))
5
+ - alf: |-
6
+ allbut(union(suppliers_in_london, suppliers_in_paris), [:name])
7
+ optimized: |-
8
+ union(allbut(suppliers_in_london, [:name]), allbut(suppliers_in_paris, [:name]))
@@ -0,0 +1,4 @@
1
+ - alf: |-
2
+ restrict(clip(suppliers, [:sid, :name]), city: 'Paris')
3
+ optimized: |-
4
+ clip(restrict(suppliers, city: 'Paris'), [:sid, :name])
@@ -0,0 +1,4 @@
1
+ - alf: |-
2
+ restrict(compact(suppliers), city: 'Paris')
3
+ optimized: |-
4
+ compact(restrict(suppliers, city: 'Paris'))
@@ -0,0 +1,4 @@
1
+ - alf: |-
2
+ restrict(generator(10, :auto), auto: 2)
3
+ optimized: |-
4
+ restrict(generator(10, :auto), auto: 2)
@@ -0,0 +1,4 @@
1
+ - alf: |-
2
+ restrict(intersect(suppliers, suppliers), city: 'Paris')
3
+ optimized: |-
4
+ intersect(restrict(suppliers, city: 'Paris'), restrict(suppliers, city: 'Paris'))
@@ -0,0 +1,4 @@
1
+ - alf: |-
2
+ restrict(suppliers, city: 'Paris')
3
+ optimized: |-
4
+ restrict(suppliers, city: 'Paris')
@@ -0,0 +1,4 @@
1
+ - alf: |-
2
+ restrict(minus(suppliers, suppliers), city: 'Paris')
3
+ optimized: |-
4
+ minus(restrict(suppliers, city: 'Paris'), restrict(suppliers, city: 'Paris'))
@@ -0,0 +1,12 @@
1
+ # restrict of a page is not optimizable
2
+ - alf: |-
3
+ restrict(page(suppliers, [:name], 1, page_size: 5), city: 'Paris')
4
+ optimized: |-
5
+ restrict(page(suppliers, [:name], 1, page_size: 5), city: 'Paris')
6
+ # ... but the inner operand is
7
+ - alf: |-
8
+ inside = restrict(compact(suppliers), city: 'Paris')
9
+ restrict(page(inside, [:name], 1, page_size: 5), city: 'Paris')
10
+ optimized: |-
11
+ inside = compact(restrict(suppliers, city: 'Paris'))
12
+ restrict(page(inside, [:name], 1, page_size: 5), city: 'Paris')