axiom-sql-generator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +37 -0
  4. data/.rspec +4 -0
  5. data/.rvmrc +1 -0
  6. data/.travis.yml +35 -0
  7. data/CONTRIBUTING.md +11 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.devtools +57 -0
  10. data/Guardfile +23 -0
  11. data/LICENSE +20 -0
  12. data/README.md +70 -0
  13. data/Rakefile +5 -0
  14. data/TODO +34 -0
  15. data/axiom-sql-generator.gemspec +25 -0
  16. data/config/flay.yml +3 -0
  17. data/config/flog.yml +2 -0
  18. data/config/mutant.yml +3 -0
  19. data/config/reek.yml +165 -0
  20. data/config/yardstick.yml +2 -0
  21. data/lib/axiom-sql-generator.rb +3 -0
  22. data/lib/axiom/sql/generator.rb +61 -0
  23. data/lib/axiom/sql/generator/attribute.rb +25 -0
  24. data/lib/axiom/sql/generator/core_ext/date.rb +20 -0
  25. data/lib/axiom/sql/generator/core_ext/date_time.rb +46 -0
  26. data/lib/axiom/sql/generator/direction.rb +38 -0
  27. data/lib/axiom/sql/generator/function.rb +55 -0
  28. data/lib/axiom/sql/generator/function/aggregate.rb +134 -0
  29. data/lib/axiom/sql/generator/function/connective.rb +53 -0
  30. data/lib/axiom/sql/generator/function/numeric.rb +135 -0
  31. data/lib/axiom/sql/generator/function/predicate.rb +266 -0
  32. data/lib/axiom/sql/generator/function/proposition.rb +38 -0
  33. data/lib/axiom/sql/generator/function/string.rb +29 -0
  34. data/lib/axiom/sql/generator/identifier.rb +28 -0
  35. data/lib/axiom/sql/generator/literal.rb +157 -0
  36. data/lib/axiom/sql/generator/relation.rb +240 -0
  37. data/lib/axiom/sql/generator/relation/base.rb +14 -0
  38. data/lib/axiom/sql/generator/relation/binary.rb +136 -0
  39. data/lib/axiom/sql/generator/relation/insertion.rb +62 -0
  40. data/lib/axiom/sql/generator/relation/materialized.rb +60 -0
  41. data/lib/axiom/sql/generator/relation/set.rb +107 -0
  42. data/lib/axiom/sql/generator/relation/unary.rb +379 -0
  43. data/lib/axiom/sql/generator/version.rb +12 -0
  44. data/lib/axiom/sql/generator/visitor.rb +121 -0
  45. data/spec/rcov.opts +7 -0
  46. data/spec/shared/generated_sql_behavior.rb +15 -0
  47. data/spec/spec_helper.rb +33 -0
  48. data/spec/support/config_alias.rb +3 -0
  49. data/spec/unit/axiom/sql/generator/attribute/visit_axiom_attribute_spec.rb +15 -0
  50. data/spec/unit/axiom/sql/generator/class_methods/parenthesize_spec.rb +18 -0
  51. data/spec/unit/axiom/sql/generator/direction/visit_axiom_relation_operation_order_ascending_spec.rb +15 -0
  52. data/spec/unit/axiom/sql/generator/direction/visit_axiom_relation_operation_order_descending_spec.rb +15 -0
  53. data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_count_spec.rb +16 -0
  54. data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_maximum_spec.rb +16 -0
  55. data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_mean_spec.rb +16 -0
  56. data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_minimum_spec.rb +16 -0
  57. data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_standard_deviation_spec.rb +16 -0
  58. data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_sum_spec.rb +16 -0
  59. data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_variance_spec.rb +16 -0
  60. data/spec/unit/axiom/sql/generator/function/connective/visit_axiom_function_connective_conjunction_spec.rb +20 -0
  61. data/spec/unit/axiom/sql/generator/function/connective/visit_axiom_function_connective_disjunction_spec.rb +20 -0
  62. data/spec/unit/axiom/sql/generator/function/connective/visit_axiom_function_connective_negation_spec.rb +20 -0
  63. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_absolute_spec.rb +15 -0
  64. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_addition_spec.rb +15 -0
  65. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_division_spec.rb +15 -0
  66. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_exponentiation_spec.rb +15 -0
  67. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_modulo_spec.rb +15 -0
  68. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_multiplication_spec.rb +15 -0
  69. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_square_root_spec.rb +15 -0
  70. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_subtraction_spec.rb +15 -0
  71. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_unary_minus_spec.rb +15 -0
  72. data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_unary_plus_spec.rb +15 -0
  73. data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_equality_spec.rb +27 -0
  74. data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_exclusion_spec.rb +47 -0
  75. data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_greater_than_or_equal_to_spec.rb +15 -0
  76. data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_greater_than_spec.rb +15 -0
  77. data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_inclusion_spec.rb +47 -0
  78. data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_inequality_spec.rb +55 -0
  79. data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_less_than_or_equal_to_spec.rb +15 -0
  80. data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_less_than_spec.rb +15 -0
  81. data/spec/unit/axiom/sql/generator/function/proposition/visit_axiom_function_proposition_contradiction_spec.rb +15 -0
  82. data/spec/unit/axiom/sql/generator/function/proposition/visit_axiom_function_proposition_tautology_spec.rb +15 -0
  83. data/spec/unit/axiom/sql/generator/function/string/visit_axiom_function_string_length_spec.rb +15 -0
  84. data/spec/unit/axiom/sql/generator/identifier/visit_identifier_spec.rb +26 -0
  85. data/spec/unit/axiom/sql/generator/literal/class_methods/dup_frozen_spec.rb +23 -0
  86. data/spec/unit/axiom/sql/generator/literal/visit_class_spec.rb +31 -0
  87. data/spec/unit/axiom/sql/generator/literal/visit_date_spec.rb +15 -0
  88. data/spec/unit/axiom/sql/generator/literal/visit_date_time_spec.rb +55 -0
  89. data/spec/unit/axiom/sql/generator/literal/visit_enumerable_spec.rb +15 -0
  90. data/spec/unit/axiom/sql/generator/literal/visit_false_class_spec.rb +14 -0
  91. data/spec/unit/axiom/sql/generator/literal/visit_nil_class_spec.rb +14 -0
  92. data/spec/unit/axiom/sql/generator/literal/visit_numeric_spec.rb +34 -0
  93. data/spec/unit/axiom/sql/generator/literal/visit_string_spec.rb +26 -0
  94. data/spec/unit/axiom/sql/generator/literal/visit_time_spec.rb +97 -0
  95. data/spec/unit/axiom/sql/generator/literal/visit_true_class_spec.rb +14 -0
  96. data/spec/unit/axiom/sql/generator/relation/binary/base/to_subquery_spec.rb +35 -0
  97. data/spec/unit/axiom/sql/generator/relation/binary/base/visit_axiom_relation_base_spec.rb +22 -0
  98. data/spec/unit/axiom/sql/generator/relation/binary/to_s_spec.rb +35 -0
  99. data/spec/unit/axiom/sql/generator/relation/binary/to_subquery_spec.rb +35 -0
  100. data/spec/unit/axiom/sql/generator/relation/binary/visit_axiom_algebra_join_spec.rb +179 -0
  101. data/spec/unit/axiom/sql/generator/relation/binary/visit_axiom_algebra_product_spec.rb +183 -0
  102. data/spec/unit/axiom/sql/generator/relation/class_methods/visit_spec.rb +71 -0
  103. data/spec/unit/axiom/sql/generator/relation/insertion/to_subquery_spec.rb +15 -0
  104. data/spec/unit/axiom/sql/generator/relation/insertion/visit_axiom_relation_operation_insertion_spec.rb +187 -0
  105. data/spec/unit/axiom/sql/generator/relation/materialized/visit_axiom_relation_materialized_spec.rb +28 -0
  106. data/spec/unit/axiom/sql/generator/relation/materialized/visited_spec.rb +26 -0
  107. data/spec/unit/axiom/sql/generator/relation/name_spec.rb +30 -0
  108. data/spec/unit/axiom/sql/generator/relation/set/class_methods/normalize_operand_headers_spec.rb +35 -0
  109. data/spec/unit/axiom/sql/generator/relation/set/to_s_spec.rb +55 -0
  110. data/spec/unit/axiom/sql/generator/relation/set/to_subquery_spec.rb +55 -0
  111. data/spec/unit/axiom/sql/generator/relation/set/visit_axiom_algebra_difference_spec.rb +191 -0
  112. data/spec/unit/axiom/sql/generator/relation/set/visit_axiom_algebra_intersection_spec.rb +188 -0
  113. data/spec/unit/axiom/sql/generator/relation/set/visit_axiom_algebra_union_spec.rb +188 -0
  114. data/spec/unit/axiom/sql/generator/relation/to_s_spec.rb +50 -0
  115. data/spec/unit/axiom/sql/generator/relation/to_sql_spec.rb +52 -0
  116. data/spec/unit/axiom/sql/generator/relation/to_subquery_spec.rb +49 -0
  117. data/spec/unit/axiom/sql/generator/relation/unary/to_s_spec.rb +55 -0
  118. data/spec/unit/axiom/sql/generator/relation/unary/to_subquery_spec.rb +75 -0
  119. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_extension_spec.rb +165 -0
  120. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_projection_spec.rb +193 -0
  121. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_rename_spec.rb +178 -0
  122. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_restriction_spec.rb +199 -0
  123. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_summarization_spec.rb +652 -0
  124. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_base_spec.rb +21 -0
  125. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_limit_spec.rb +165 -0
  126. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_offset_spec.rb +165 -0
  127. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_order_spec.rb +183 -0
  128. data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_reverse_spec.rb +165 -0
  129. data/spec/unit/axiom/sql/generator/relation/visit_spec.rb +54 -0
  130. data/spec/unit/axiom/sql/generator/relation/visited_spec.rb +35 -0
  131. data/spec/unit/axiom/sql/generator/visitor/class_methods/handler_for_spec.rb +71 -0
  132. data/spec/unit/axiom/sql/generator/visitor/visit_spec.rb +12 -0
  133. data/spec/unit/axiom/sql/generator/visitor/visited_spec.rb +11 -0
  134. data/spec/unit/date/iso8601_spec.rb +23 -0
  135. data/spec/unit/date_time/iso8601_spec.rb +112 -0
  136. metadata +325 -0
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Binary, '#to_s' do
6
+ subject { object.to_s }
7
+
8
+ let(:id) { Attribute::Integer.new(:id) }
9
+ let(:name) { Attribute::String.new(:name) }
10
+ let(:age) { Attribute::Integer.new(:age, :required => false) }
11
+ let(:header) { [ id, name, age ] }
12
+ let(:body) { [ [ 1, 'Dan Kubb', 35 ] ].each }
13
+ let(:base_relation) { Relation::Base.new('users', header, body) }
14
+ let(:object) { described_class.new }
15
+
16
+ context 'when no object visited' do
17
+ it_should_behave_like 'an idempotent method'
18
+
19
+ it { should respond_to(:to_s) }
20
+
21
+ it { should be_frozen }
22
+
23
+ its(:to_s) { should == '' }
24
+ end
25
+
26
+ context 'when a join is visited' do
27
+ before do
28
+ object.visit(base_relation.join(base_relation))
29
+ end
30
+
31
+ it_should_behave_like 'a generated SQL expression'
32
+
33
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "users" AS "right"') }
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Binary, '#to_subquery' do
6
+ subject { object.to_subquery }
7
+
8
+ let(:id) { Attribute::Integer.new(:id) }
9
+ let(:name) { Attribute::String.new(:name) }
10
+ let(:age) { Attribute::Integer.new(:age, :required => false) }
11
+ let(:header) { [ id, name, age ] }
12
+ let(:body) { [ [ 1, 'Dan Kubb', 35 ] ].each }
13
+ let(:base_relation) { Relation::Base.new('users', header, body) }
14
+ let(:object) { described_class.new }
15
+
16
+ context 'when no object visited' do
17
+ it_should_behave_like 'an idempotent method'
18
+
19
+ it { should respond_to(:to_s) }
20
+
21
+ it { should be_frozen }
22
+
23
+ its(:to_s) { should == '' }
24
+ end
25
+
26
+ context 'when a join is visited' do
27
+ before do
28
+ object.visit(base_relation.join(base_relation))
29
+ end
30
+
31
+ it_should_behave_like 'a generated SQL expression'
32
+
33
+ its(:to_s) { should eql('(SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right")') }
34
+ end
35
+ end
@@ -0,0 +1,179 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Binary, '#visit_axiom_algebra_join' do
6
+ subject { object.visit_axiom_algebra_join(join) }
7
+
8
+ let(:relation_name) { 'users' }
9
+ let(:id) { Attribute::Integer.new(:id) }
10
+ let(:name) { Attribute::String.new(:name) }
11
+ let(:age) { Attribute::Integer.new(:age, :required => false) }
12
+ let(:header) { [ id, name, age ] }
13
+ let(:body) { [ [ 1, 'Dan Kubb', 35 ] ].each }
14
+ let(:base_relation) { Relation::Base.new(relation_name, header, body) }
15
+ let(:other_relation) { Relation::Base.new('other', header, body) }
16
+ let(:left) { operand }
17
+ let(:right) { operand }
18
+ let(:join) { left.join(right) }
19
+ let(:object) { described_class.new }
20
+
21
+ context 'when the operands are base relations' do
22
+ let(:operand) { base_relation }
23
+
24
+ it_should_behave_like 'a generated SQL SELECT query'
25
+
26
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "users" AS "right"') }
27
+ its(:to_subquery) { should eql('(SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right")') }
28
+ end
29
+
30
+ context 'when the operands are projections' do
31
+ let(:operand) { base_relation.project([ :id, :name ]) }
32
+
33
+ it_should_behave_like 'a generated SQL SELECT query'
34
+
35
+ its(:to_s) { should eql('SELECT "id", "name" FROM (SELECT DISTINCT "id", "name" FROM "users") AS "left" NATURAL JOIN (SELECT DISTINCT "id", "name" FROM "users") AS "right"') }
36
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT DISTINCT "id", "name" FROM "users") AS "left" NATURAL JOIN (SELECT DISTINCT "id", "name" FROM "users") AS "right")') }
37
+ end
38
+
39
+ context 'when the operand is an extension' do
40
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) } }
41
+
42
+ it_should_behave_like 'a generated SQL SELECT query'
43
+
44
+ its(:to_s) { should eql('SELECT "id", "name", "age", "one" FROM (SELECT *, 1 AS "one" FROM "users") AS "left" NATURAL JOIN (SELECT *, 1 AS "one" FROM "users") AS "right"') }
45
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT *, 1 AS "one" FROM "users") AS "left" NATURAL JOIN (SELECT *, 1 AS "one" FROM "users") AS "right")') }
46
+ end
47
+
48
+ context 'when the operands are rename' do
49
+ let(:operand) { base_relation.rename(:id => :user_id) }
50
+
51
+ it_should_behave_like 'a generated SQL SELECT query'
52
+
53
+ its(:to_s) { should eql('SELECT "user_id", "name", "age" FROM (SELECT "id" AS "user_id", "name", "age" FROM "users") AS "left" NATURAL JOIN (SELECT "id" AS "user_id", "name", "age" FROM "users") AS "right"') }
54
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT "id" AS "user_id", "name", "age" FROM "users") AS "left" NATURAL JOIN (SELECT "id" AS "user_id", "name", "age" FROM "users") AS "right")') }
55
+ end
56
+
57
+ context 'when the operands are restrictions' do
58
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
59
+
60
+ it_should_behave_like 'a generated SQL SELECT query'
61
+
62
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" WHERE "id" = 1) AS "left" NATURAL JOIN (SELECT * FROM "users" WHERE "id" = 1) AS "right"') }
63
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" WHERE "id" = 1) AS "left" NATURAL JOIN (SELECT * FROM "users" WHERE "id" = 1) AS "right")') }
64
+ end
65
+
66
+ context 'when the operand is a summarization' do
67
+ context 'summarize per table dee' do
68
+ let(:summarize_per) { TABLE_DEE }
69
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
70
+
71
+ it_should_behave_like 'a generated SQL SELECT query'
72
+
73
+ its(:to_s) { should eql('SELECT "count" FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "left" NATURAL JOIN (SELECT COUNT ("id") AS "count" FROM "users") AS "right"') }
74
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "left" NATURAL JOIN (SELECT COUNT ("id") AS "count" FROM "users") AS "right")') }
75
+ end
76
+
77
+ context 'summarize per table dum' do
78
+ let(:summarize_per) { TABLE_DUM }
79
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
80
+
81
+ it_should_behave_like 'a generated SQL SELECT query'
82
+
83
+ its(:to_s) { should eql('SELECT "count" FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "left" NATURAL JOIN (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "right"') }
84
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "left" NATURAL JOIN (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "right")') }
85
+ end
86
+
87
+ context 'summarize by a subset of the operand header' do
88
+ let(:operand) { base_relation.summarize([ :id, :name ]) { |r| r.add(:count, r.age.count) } }
89
+
90
+ it_should_behave_like 'a generated SQL SELECT query'
91
+
92
+ its(:to_s) { should eql('SELECT "id", "name", "count" FROM (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) AS "left" NATURAL JOIN (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) AS "right"') }
93
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) AS "left" NATURAL JOIN (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) AS "right")') }
94
+ end
95
+ end
96
+
97
+ context 'when the operands are ordered' do
98
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
99
+
100
+ it_should_behave_like 'a generated SQL SELECT query'
101
+
102
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "left" NATURAL JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "right"') }
103
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "left" NATURAL JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "right")') }
104
+ end
105
+
106
+ context 'when the operands are reversed' do
107
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
108
+
109
+ it_should_behave_like 'a generated SQL SELECT query'
110
+
111
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "left" NATURAL JOIN (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "right"') }
112
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "left" NATURAL JOIN (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "right")') }
113
+ end
114
+
115
+ context 'when the operands are limited' do
116
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
117
+
118
+ it_should_behave_like 'a generated SQL SELECT query'
119
+
120
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "left" NATURAL JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "right"') }
121
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "left" NATURAL JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "right")') }
122
+ end
123
+
124
+ context 'when the operands are offsets' do
125
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
126
+
127
+ it_should_behave_like 'a generated SQL SELECT query'
128
+
129
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "left" NATURAL JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "right"') }
130
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "left" NATURAL JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "right")') }
131
+ end
132
+
133
+ context 'when the operands are differences' do
134
+ let(:operand) { base_relation.difference(base_relation) }
135
+
136
+ it_should_behave_like 'a generated SQL SELECT query'
137
+
138
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "left" NATURAL JOIN ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "right"') }
139
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "left" NATURAL JOIN ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "right")') }
140
+ end
141
+
142
+ context 'when the operands are intersections' do
143
+ let(:operand) { base_relation.intersect(base_relation) }
144
+
145
+ it_should_behave_like 'a generated SQL SELECT query'
146
+
147
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "left" NATURAL JOIN ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "right"') }
148
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "left" NATURAL JOIN ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "right")') }
149
+ end
150
+
151
+ context 'when the operands are unions' do
152
+ let(:operand) { base_relation.union(base_relation) }
153
+
154
+ it_should_behave_like 'a generated SQL SELECT query'
155
+
156
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "left" NATURAL JOIN ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "right"') }
157
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "left" NATURAL JOIN ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "right")') }
158
+ end
159
+
160
+ context 'when the operands are joins' do
161
+ let(:operand) { base_relation.join(base_relation) }
162
+
163
+ it_should_behave_like 'a generated SQL SELECT query'
164
+
165
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "left" NATURAL JOIN (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "right"') }
166
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "left" NATURAL JOIN (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "right")') }
167
+ end
168
+
169
+ context 'when the operands have different base relations' do
170
+ let(:relation_name) { 'users_others' }
171
+ let(:left) { Relation::Base.new('users', header, body) }
172
+ let(:right) { Relation::Base.new('others', header, body) }
173
+
174
+ it_should_behave_like 'a generated SQL SELECT query'
175
+
176
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "others" AS "right"') }
177
+ its(:to_subquery) { should eql('(SELECT * FROM "users" AS "left" NATURAL JOIN "others" AS "right")') }
178
+ end
179
+ end
@@ -0,0 +1,183 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Binary, '#visit_axiom_algebra_product' do
6
+ subject { object.visit_axiom_algebra_product(product) }
7
+
8
+ let(:relation_name) { 'users_other' }
9
+ let(:id) { Attribute::Integer.new(:id) }
10
+ let(:name) { Attribute::String.new(:name) }
11
+ let(:age) { Attribute::Integer.new(:age, :required => false) }
12
+ let(:header) { [ id, name, age ] }
13
+ let(:other_header) { [ id.rename(:other_id), name.rename(:other_name), age.rename(:other_age) ] }
14
+ let(:body) { [ [ 1, 'Dan Kubb', 35 ] ].each }
15
+ let(:users) { Relation::Base.new('users', header, body) }
16
+ let(:other) { Relation::Base.new('other', other_header, body) }
17
+ let(:product) { left.product(right) }
18
+ let(:object) { described_class.new }
19
+
20
+ context 'when the operands are base relations' do
21
+ let(:left) { users }
22
+ let(:right) { other }
23
+
24
+ it_should_behave_like 'a generated SQL SELECT query'
25
+
26
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM "users" AS "left" CROSS JOIN "other" AS "right"') }
27
+ its(:to_subquery) { should eql('(SELECT * FROM "users" AS "left" CROSS JOIN "other" AS "right")') }
28
+ end
29
+
30
+ context 'when the operands are a projection' do
31
+ let(:left) { users.project([ :id, :name ]) }
32
+ let(:right) { other.project([ :other_id, :other_name ]) }
33
+
34
+ it_should_behave_like 'a generated SQL SELECT query'
35
+
36
+ its(:to_s) { should eql('SELECT "id", "name", "other_id", "other_name" FROM (SELECT DISTINCT "id", "name" FROM "users") AS "left" CROSS JOIN (SELECT DISTINCT "other_id", "other_name" FROM "other") AS "right"') }
37
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT DISTINCT "id", "name" FROM "users") AS "left" CROSS JOIN (SELECT DISTINCT "other_id", "other_name" FROM "other") AS "right")') }
38
+ end
39
+
40
+ context 'when the operand is an extension' do
41
+ let(:left) { users.extend { |r| r.add(:one, 1) } }
42
+ let(:right) { other.extend { |r| r.add(:two, 2) } }
43
+
44
+ it_should_behave_like 'a generated SQL SELECT query'
45
+
46
+ its(:to_s) { should eql('SELECT "id", "name", "age", "one", "other_id", "other_name", "other_age", "two" FROM (SELECT *, 1 AS "one" FROM "users") AS "left" CROSS JOIN (SELECT *, 2 AS "two" FROM "other") AS "right"') }
47
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT *, 1 AS "one" FROM "users") AS "left" CROSS JOIN (SELECT *, 2 AS "two" FROM "other") AS "right")') }
48
+ end
49
+
50
+ context 'when the operand is a rename' do
51
+ let(:left) { users.rename(:id => :user_id) }
52
+ let(:right) { other.rename(:other_id => :other_user_id) }
53
+
54
+ it_should_behave_like 'a generated SQL SELECT query'
55
+
56
+ its(:to_s) { should eql('SELECT "user_id", "name", "age", "other_user_id", "other_name", "other_age" FROM (SELECT "id" AS "user_id", "name", "age" FROM "users") AS "left" CROSS JOIN (SELECT "other_id" AS "other_user_id", "other_name", "other_age" FROM "other") AS "right"') }
57
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT "id" AS "user_id", "name", "age" FROM "users") AS "left" CROSS JOIN (SELECT "other_id" AS "other_user_id", "other_name", "other_age" FROM "other") AS "right")') }
58
+ end
59
+
60
+ context 'when the operand is a restriction' do
61
+ let(:left) { users.restrict { |r| r.id.eq(1) } }
62
+ let(:right) { other.restrict { |r| r.other_id.eq(1) } }
63
+
64
+ it_should_behave_like 'a generated SQL SELECT query'
65
+
66
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM (SELECT * FROM "users" WHERE "id" = 1) AS "left" CROSS JOIN (SELECT * FROM "other" WHERE "other_id" = 1) AS "right"') }
67
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" WHERE "id" = 1) AS "left" CROSS JOIN (SELECT * FROM "other" WHERE "other_id" = 1) AS "right")') }
68
+ end
69
+
70
+ context 'when the operand is a summarization' do
71
+ context 'summarize per table dee' do
72
+ let(:summarize_per) { TABLE_DEE }
73
+ let(:left) { users.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
74
+ let(:right) { other.summarize(summarize_per) { |r| r.add(:other_count, r.other_id.count) } }
75
+
76
+ it_should_behave_like 'a generated SQL SELECT query'
77
+
78
+ its(:to_s) { should eql('SELECT "count", "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "left" CROSS JOIN (SELECT COUNT ("other_id") AS "other_count" FROM "other") AS "right"') }
79
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "left" CROSS JOIN (SELECT COUNT ("other_id") AS "other_count" FROM "other") AS "right")') }
80
+ end
81
+
82
+ context 'summarize per table dum' do
83
+ let(:summarize_per) { TABLE_DUM }
84
+ let(:left) { users.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
85
+ let(:right) { other.summarize(summarize_per) { |r| r.add(:other_count, r.other_id.count) } }
86
+
87
+ it_should_behave_like 'a generated SQL SELECT query'
88
+
89
+ its(:to_s) { should eql('SELECT "count", "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "left" CROSS JOIN (SELECT COUNT ("other_id") AS "other_count" FROM "other" HAVING FALSE) AS "right"') }
90
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "left" CROSS JOIN (SELECT COUNT ("other_id") AS "other_count" FROM "other" HAVING FALSE) AS "right")') }
91
+ end
92
+
93
+ context 'summarize by a subset of the operand header' do
94
+ let(:left) { users.summarize([ :id, :name ]) { |r| r.add(:count, r.age.count) } }
95
+ let(:right) { other.summarize([ :other_id, :other_name ]) { |r| r.add(:other_count, r.other_age.count) } }
96
+
97
+ it_should_behave_like 'a generated SQL SELECT query'
98
+
99
+ its(:to_s) { should eql('SELECT "id", "name", "count", "other_id", "other_name", "other_count" FROM (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) AS "left" CROSS JOIN (SELECT "other_id", "other_name", COUNT ("other_age") AS "other_count" FROM "other" GROUP BY "other_id", "other_name" HAVING COUNT (*) > 0) AS "right"') }
100
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) AS "left" CROSS JOIN (SELECT "other_id", "other_name", COUNT ("other_age") AS "other_count" FROM "other" GROUP BY "other_id", "other_name" HAVING COUNT (*) > 0) AS "right")') }
101
+ end
102
+ end
103
+
104
+ context 'when the operand is ordered' do
105
+ let(:left) { users.sort_by { |r| [ r.id, r.name, r.age ] } }
106
+ let(:right) { other.sort_by { |r| [ r.other_id, r.other_name, r.other_age ] } }
107
+
108
+ it_should_behave_like 'a generated SQL SELECT query'
109
+
110
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "left" CROSS JOIN (SELECT * FROM "other" ORDER BY "other_id", "other_name", "other_age") AS "right"') }
111
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "left" CROSS JOIN (SELECT * FROM "other" ORDER BY "other_id", "other_name", "other_age") AS "right")') }
112
+ end
113
+
114
+ context 'when the operand is reversed' do
115
+ let(:left) { users.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
116
+ let(:right) { other.sort_by { |r| [ r.other_id, r.other_name, r.other_age ] }.reverse }
117
+
118
+ it_should_behave_like 'a generated SQL SELECT query'
119
+
120
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "left" CROSS JOIN (SELECT * FROM "other" ORDER BY "other_id" DESC, "other_name" DESC, "other_age" DESC) AS "right"') }
121
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "left" CROSS JOIN (SELECT * FROM "other" ORDER BY "other_id" DESC, "other_name" DESC, "other_age" DESC) AS "right")') }
122
+ end
123
+
124
+ context 'when the operand is limited' do
125
+ let(:left) { users.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
126
+ let(:right) { other.sort_by { |r| [ r.other_id, r.other_name, r.other_age ] }.take(1) }
127
+
128
+ it_should_behave_like 'a generated SQL SELECT query'
129
+
130
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "left" CROSS JOIN (SELECT * FROM "other" ORDER BY "other_id", "other_name", "other_age" LIMIT 1) AS "right"') }
131
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "left" CROSS JOIN (SELECT * FROM "other" ORDER BY "other_id", "other_name", "other_age" LIMIT 1) AS "right")') }
132
+ end
133
+
134
+ context 'when the operand is an offset' do
135
+ let(:left) { users.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
136
+ let(:right) { other.sort_by { |r| [ r.other_id, r.other_name, r.other_age ] }.drop(1) }
137
+
138
+ it_should_behave_like 'a generated SQL SELECT query'
139
+
140
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "left" CROSS JOIN (SELECT * FROM "other" ORDER BY "other_id", "other_name", "other_age" OFFSET 1) AS "right"') }
141
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "left" CROSS JOIN (SELECT * FROM "other" ORDER BY "other_id", "other_name", "other_age" OFFSET 1) AS "right")') }
142
+ end
143
+
144
+ context 'when the operand is a difference' do
145
+ let(:left) { users.difference(users) }
146
+ let(:right) { other.difference(other) }
147
+
148
+ it_should_behave_like 'a generated SQL SELECT query'
149
+
150
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "left" CROSS JOIN ((SELECT "other_id", "other_name", "other_age" FROM "other") EXCEPT (SELECT "other_id", "other_name", "other_age" FROM "other")) AS "right"') }
151
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "left" CROSS JOIN ((SELECT "other_id", "other_name", "other_age" FROM "other") EXCEPT (SELECT "other_id", "other_name", "other_age" FROM "other")) AS "right")') }
152
+ end
153
+
154
+ context 'when the operand is a intersection' do
155
+ let(:left) { users.intersect(users) }
156
+ let(:right) { other.intersect(other) }
157
+
158
+ it_should_behave_like 'a generated SQL SELECT query'
159
+
160
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "left" CROSS JOIN ((SELECT "other_id", "other_name", "other_age" FROM "other") INTERSECT (SELECT "other_id", "other_name", "other_age" FROM "other")) AS "right"') }
161
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "left" CROSS JOIN ((SELECT "other_id", "other_name", "other_age" FROM "other") INTERSECT (SELECT "other_id", "other_name", "other_age" FROM "other")) AS "right")') }
162
+ end
163
+
164
+ context 'when the operand is a union' do
165
+ let(:left) { users.union(users) }
166
+ let(:right) { other.union(other) }
167
+
168
+ it_should_behave_like 'a generated SQL SELECT query'
169
+
170
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "left" CROSS JOIN ((SELECT "other_id", "other_name", "other_age" FROM "other") UNION (SELECT "other_id", "other_name", "other_age" FROM "other")) AS "right"') }
171
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "left" CROSS JOIN ((SELECT "other_id", "other_name", "other_age" FROM "other") UNION (SELECT "other_id", "other_name", "other_age" FROM "other")) AS "right")') }
172
+ end
173
+
174
+ context 'when the operand is a join' do
175
+ let(:left) { users.join(users) }
176
+ let(:right) { other.join(other) }
177
+
178
+ it_should_behave_like 'a generated SQL SELECT query'
179
+
180
+ its(:to_s) { should eql('SELECT "id", "name", "age", "other_id", "other_name", "other_age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "left" CROSS JOIN (SELECT * FROM "other" AS "left" NATURAL JOIN "other" AS "right") AS "right"') }
181
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "left" CROSS JOIN (SELECT * FROM "other" AS "left" NATURAL JOIN "other" AS "right") AS "right")') }
182
+ end
183
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation, '.visit' do
6
+ subject { object.visit(relation) }
7
+
8
+ let(:id) { Attribute::Integer.new(:id) }
9
+ let(:name) { Attribute::String.new(:name) }
10
+ let(:age) { Attribute::Integer.new(:age, :required => false) }
11
+ let(:header) { [ id, name, age ] }
12
+ let(:body) { [ [ 1, 'Dan Kubb', 35 ] ].each }
13
+ let(:base_relation) { Relation::Base.new('users', header, body) }
14
+ let(:object) { described_class }
15
+
16
+ context 'when the relation is an insertion operation' do
17
+ let(:relation) { base_relation.insert(base_relation) }
18
+
19
+ it { should be_kind_of(SQL::Generator::Relation::Insertion) }
20
+
21
+ its(:name) { should == 'users' }
22
+
23
+ it { should be_frozen }
24
+ end
25
+
26
+ context 'when the relation is a set operation' do
27
+ let(:relation) { base_relation.union(base_relation) }
28
+
29
+ it { should be_kind_of(SQL::Generator::Relation::Set) }
30
+
31
+ its(:name) { should == 'users' }
32
+
33
+ it { should be_frozen }
34
+ end
35
+
36
+ context 'when the relation is a binary operation' do
37
+ let(:relation) { base_relation.join(base_relation.project([ :id ])) }
38
+
39
+ it { should be_kind_of(SQL::Generator::Relation::Binary) }
40
+
41
+ its(:name) { should == 'users' }
42
+
43
+ it { should be_frozen }
44
+ end
45
+
46
+ context 'when the relation is a unary operation' do
47
+ let(:relation) { base_relation.project([ :id ]) }
48
+
49
+ it { should be_kind_of(SQL::Generator::Relation::Unary) }
50
+
51
+ its(:name) { should == 'users' }
52
+
53
+ it { should be_frozen }
54
+ end
55
+
56
+ context 'when the relation is a base relation' do
57
+ let(:relation) { base_relation }
58
+
59
+ it { should be_kind_of(SQL::Generator::Relation::Base) }
60
+
61
+ its(:name) { should == 'users' }
62
+
63
+ it { should be_frozen }
64
+ end
65
+
66
+ context 'when the relation is invalid' do
67
+ let(:relation) { mock('Invalid Relation') }
68
+
69
+ specify { expect { subject }.to raise_error(SQL::Generator::InvalidRelationError, "#{relation.class} is not a visitable relation") }
70
+ end
71
+ end