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,178 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Unary, '#visit_axiom_algebra_rename' do
6
+ subject { object.visit_axiom_algebra_rename(rename) }
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(:rename) { operand.rename(:id => :user_id) }
16
+ let(:object) { described_class.new }
17
+
18
+ context 'when the operand is a base relation' do
19
+ let(:operand) { base_relation }
20
+
21
+ it_should_behave_like 'a generated SQL SELECT query'
22
+
23
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users"') }
24
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users")') }
25
+ end
26
+
27
+ context 'when the operand is a projection' do
28
+ let(:operand) { base_relation.project([ :id, :name ]) }
29
+
30
+ it_should_behave_like 'a generated SQL SELECT query'
31
+
32
+ its(:to_s) { should eql('SELECT DISTINCT "id" AS "user_id", "name" FROM "users"') }
33
+ its(:to_subquery) { should eql('(SELECT DISTINCT "id" AS "user_id", "name" FROM "users")') }
34
+ end
35
+
36
+ context 'when the operand is an extension' do
37
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) } }
38
+
39
+ it_should_behave_like 'a generated SQL SELECT query'
40
+
41
+ its(:to_s) { pending { should eql('SELECT "id" AS "user_id", "name", "age", 1 AS "one" FROM "users"') } }
42
+ its(:to_subquery) { pending { should eql('SELECT "id" AS "user_id", "name", "age", 1 AS "one" FROM "users"') } }
43
+ end
44
+
45
+ context 'when the operand is a rename' do
46
+ let(:operand) { base_relation.rename(:name => :other_name) }
47
+
48
+ context 'when the relation is not optimized' do
49
+ it_should_behave_like 'a generated SQL SELECT query'
50
+
51
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "other_name", "age" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users"') }
52
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "other_name", "age" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users")') }
53
+ end
54
+
55
+ context 'when the operand is empty' do
56
+ let(:operand) { base_relation.rename({}) }
57
+
58
+ it_should_behave_like 'a generated SQL SELECT query'
59
+
60
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM (SELECT "id", "name", "age" FROM "users") AS "users"') }
61
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM (SELECT "id", "name", "age" FROM "users") AS "users")') }
62
+ end
63
+ end
64
+
65
+ context 'when the operand is a restriction' do
66
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
67
+
68
+ it_should_behave_like 'a generated SQL SELECT query'
69
+
70
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" WHERE "id" = 1') }
71
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" WHERE "id" = 1)') }
72
+ end
73
+
74
+ context 'when the operand is a summarization' do
75
+ context 'summarize per table dee' do
76
+ let(:summarize_per) { TABLE_DEE }
77
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
78
+ let(:rename) { operand.rename(:count => :other_count) }
79
+
80
+ it_should_behave_like 'a generated SQL SELECT query'
81
+
82
+ its(:to_s) { should eql('SELECT "count" AS "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "users"') }
83
+ its(:to_subquery) { should eql('(SELECT "count" AS "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "users")') }
84
+ end
85
+
86
+ context 'summarize per table dum' do
87
+ let(:summarize_per) { TABLE_DUM }
88
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
89
+ let(:rename) { operand.rename(:count => :other_count) }
90
+
91
+ it_should_behave_like 'a generated SQL SELECT query'
92
+
93
+ its(:to_s) { should eql('SELECT "count" AS "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "users"') }
94
+ its(:to_subquery) { should eql('(SELECT "count" AS "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "users")') }
95
+ end
96
+
97
+ context 'summarize by a subset of the operand header' do
98
+ let(:operand) { base_relation.summarize([ :id, :name ]) { |r| r.add(:count, r.age.count) } }
99
+
100
+ it_should_behave_like 'a generated SQL SELECT query'
101
+
102
+ its(:to_s) { pending { should eql('SELECT "id" AS "user_id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name"') } }
103
+ its(:to_subquery) { pending { should eql('SELECT "id" AS "user_id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name"') } }
104
+ end
105
+ end
106
+
107
+ context 'when the operand is ordered' do
108
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
109
+
110
+ it_should_behave_like 'a generated SQL SELECT query'
111
+
112
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age"') }
113
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age")') }
114
+ end
115
+
116
+ context 'when the operand is reversed' do
117
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
118
+
119
+ it_should_behave_like 'a generated SQL SELECT query'
120
+
121
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') }
122
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') }
123
+ end
124
+
125
+ context 'when the operand is limited' do
126
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
127
+
128
+ it_should_behave_like 'a generated SQL SELECT query'
129
+
130
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') }
131
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') }
132
+ end
133
+
134
+ context 'when the operand is an offset' do
135
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
136
+
137
+ it_should_behave_like 'a generated SQL SELECT query'
138
+
139
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') }
140
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') }
141
+ end
142
+
143
+ context 'when the operand is a difference' do
144
+ let(:operand) { base_relation.difference(base_relation) }
145
+
146
+ it_should_behave_like 'a generated SQL SELECT query'
147
+
148
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users"') }
149
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users")') }
150
+ end
151
+
152
+ context 'when the operand is an intersection' do
153
+ let(:operand) { base_relation.intersect(base_relation) }
154
+
155
+ it_should_behave_like 'a generated SQL SELECT query'
156
+
157
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users"') }
158
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users")') }
159
+ end
160
+
161
+ context 'when the operand is a union' do
162
+ let(:operand) { base_relation.union(base_relation) }
163
+
164
+ it_should_behave_like 'a generated SQL SELECT query'
165
+
166
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users"') }
167
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users")') }
168
+ end
169
+
170
+ context 'when the operand is a join' do
171
+ let(:operand) { base_relation.join(base_relation) }
172
+
173
+ it_should_behave_like 'a generated SQL SELECT query'
174
+
175
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users"') }
176
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users")') }
177
+ end
178
+ end
@@ -0,0 +1,199 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Unary, '#visit_axiom_algebra_restriction' do
6
+ subject { object.visit_axiom_algebra_restriction(restriction) }
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(:restriction) { operand.restrict { |r| r.id.eq(1) } }
16
+ let(:object) { described_class.new }
17
+
18
+ context 'when the operand is a base relation' do
19
+ let(:operand) { base_relation }
20
+
21
+ it_should_behave_like 'a generated SQL SELECT query'
22
+
23
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1') }
24
+ its(:to_subquery) { should eql('(SELECT * FROM "users" WHERE "id" = 1)') }
25
+ end
26
+
27
+ context 'when the operand is a projection' do
28
+ let(:operand) { base_relation.project([ :id, :name ]) }
29
+
30
+ it_should_behave_like 'a generated SQL SELECT query'
31
+
32
+ its(:to_s) { should eql('SELECT DISTINCT "id", "name" FROM "users" WHERE "id" = 1') }
33
+ its(:to_subquery) { should eql('(SELECT DISTINCT "id", "name" FROM "users" WHERE "id" = 1)') }
34
+ end
35
+
36
+ context 'when the operand is an extension' do
37
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) } }
38
+
39
+ it_should_behave_like 'a generated SQL SELECT query'
40
+
41
+ its(:to_s) { should eql('SELECT "id", "name", "age", "one" FROM (SELECT *, 1 AS "one" FROM "users") AS "users" WHERE "id" = 1') }
42
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT *, 1 AS "one" FROM "users") AS "users" WHERE "id" = 1)') }
43
+ end
44
+
45
+ context 'when the operand is a projection then a restriction' do
46
+ let(:operand) { base_relation.project([ :id, :name ]).restrict { |r| r.id.ne(2) } }
47
+
48
+ it_should_behave_like 'a generated SQL SELECT query'
49
+
50
+ its(:to_s) { should eql('SELECT "id", "name" FROM (SELECT DISTINCT "id", "name" FROM "users" WHERE "id" <> 2) AS "users" WHERE "id" = 1') }
51
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT DISTINCT "id", "name" FROM "users" WHERE "id" <> 2) AS "users" WHERE "id" = 1)') }
52
+ end
53
+
54
+ context 'when the operand is a projection then a restriction, followed by another restriction' do
55
+ let(:tautology) { Function::Proposition::Tautology.instance }
56
+ let(:operand) { base_relation.project([ :id, :name ]).restrict { tautology }.restrict { tautology } }
57
+
58
+ it_should_behave_like 'a generated SQL SELECT query'
59
+
60
+ its(:to_s) { should eql('SELECT "id", "name" FROM (SELECT * FROM (SELECT DISTINCT "id", "name" FROM "users" WHERE TRUE) AS "users" WHERE TRUE) AS "users" WHERE "id" = 1') }
61
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM (SELECT DISTINCT "id", "name" FROM "users" WHERE TRUE) AS "users" WHERE TRUE) AS "users" WHERE "id" = 1)') }
62
+ end
63
+
64
+ context 'when the operand is a rename' do
65
+ context 'when the restriction includes the renamed column' do
66
+ let(:operand) { base_relation.rename(:id => :user_id) }
67
+ let(:restriction) { operand.restrict { |r| r.user_id.eq(1) } }
68
+
69
+ it_should_behave_like 'a generated SQL SELECT query'
70
+
71
+ its(:to_s) { pending { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" WHERE "id" = 1') } }
72
+ its(:to_subquery) { pending { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" WHERE "id" = 1') } }
73
+ end
74
+
75
+ context 'when the restriction does not include the renamed column' do
76
+ let(:operand) { base_relation.rename(:name => :other_name) }
77
+ let(:restriction) { operand.restrict { |r| r.id.eq(1) } }
78
+
79
+ it_should_behave_like 'a generated SQL SELECT query'
80
+
81
+ its(:to_s) { pending { should eql('SELECT "id", "name" AS "other_name", "age" FROM "users" WHERE "id" = 1') } }
82
+ its(:to_subquery) { pending { should eql('SELECT "id", "name" AS "other_name", "age" FROM "users" WHERE "id" = 1') } }
83
+ end
84
+ end
85
+
86
+ context 'when the operand is a restriction' do
87
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
88
+
89
+ it_should_behave_like 'a generated SQL SELECT query'
90
+
91
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" WHERE "id" = 1) AS "users" WHERE "id" = 1') }
92
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" WHERE "id" = 1) AS "users" WHERE "id" = 1)') }
93
+ end
94
+
95
+ context 'when the operand is a summarization' do
96
+ context 'summarize per table dee' do
97
+ let(:summarize_per) { TABLE_DEE }
98
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
99
+ let(:restriction) { operand.restrict { |r| r.count.eq(1) } }
100
+
101
+ it_should_behave_like 'a generated SQL SELECT query'
102
+
103
+ its(:to_s) { should eql('SELECT "count" FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "users" WHERE "count" = 1') }
104
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "users" WHERE "count" = 1)') }
105
+ end
106
+
107
+ context 'summarize per table dum' do
108
+ let(:summarize_per) { TABLE_DUM }
109
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
110
+ let(:restriction) { operand.restrict { |r| r.count.eq(1) } }
111
+
112
+ it_should_behave_like 'a generated SQL SELECT query'
113
+
114
+ its(:to_s) { should eql('SELECT "count" FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "users" WHERE "count" = 1') }
115
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "users" WHERE "count" = 1)') }
116
+ end
117
+
118
+ context 'summarize by a subset of the operand header' do
119
+ let(:operand) { base_relation.summarize([ :id, :name ]) { |r| r.add(:count, r.age.count) } }
120
+
121
+ it_should_behave_like 'a generated SQL SELECT query'
122
+
123
+ 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 "users" WHERE "id" = 1') }
124
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) AS "users" WHERE "id" = 1)') }
125
+ end
126
+ end
127
+
128
+ context 'when the operand is ordered' do
129
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
130
+
131
+ it_should_behave_like 'a generated SQL SELECT query'
132
+
133
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age"') }
134
+ its(:to_subquery) { should eql('(SELECT * FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age")') }
135
+ end
136
+
137
+ context 'when the operand is reversed' do
138
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
139
+
140
+ it_should_behave_like 'a generated SQL SELECT query'
141
+
142
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1 ORDER BY "id" DESC, "name" DESC, "age" DESC') }
143
+ its(:to_subquery) { should eql('(SELECT * FROM "users" WHERE "id" = 1 ORDER BY "id" DESC, "name" DESC, "age" DESC)') }
144
+ end
145
+
146
+ context 'when the operand is limited' do
147
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
148
+
149
+ it_should_behave_like 'a generated SQL SELECT query'
150
+
151
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" WHERE "id" = 1') }
152
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" WHERE "id" = 1)') }
153
+ end
154
+
155
+ context 'when the operand is an offset' do
156
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
157
+
158
+ it_should_behave_like 'a generated SQL SELECT query'
159
+
160
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" WHERE "id" = 1') }
161
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" WHERE "id" = 1)') }
162
+ end
163
+
164
+ context 'when the operand is a difference' do
165
+ let(:operand) { base_relation.difference(base_relation) }
166
+
167
+ it_should_behave_like 'a generated SQL SELECT query'
168
+
169
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" WHERE "id" = 1') }
170
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" WHERE "id" = 1)') }
171
+ end
172
+
173
+ context 'when the operand is an intersection' do
174
+ let(:operand) { base_relation.intersect(base_relation) }
175
+
176
+ it_should_behave_like 'a generated SQL SELECT query'
177
+
178
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" WHERE "id" = 1') }
179
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" WHERE "id" = 1)') }
180
+ end
181
+
182
+ context 'when the operand is a union' do
183
+ let(:operand) { base_relation.union(base_relation) }
184
+
185
+ it_should_behave_like 'a generated SQL SELECT query'
186
+
187
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" WHERE "id" = 1') }
188
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" WHERE "id" = 1)') }
189
+ end
190
+
191
+ context 'when the operand is a join' do
192
+ let(:operand) { base_relation.join(base_relation) }
193
+
194
+ it_should_behave_like 'a generated SQL SELECT query'
195
+
196
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" WHERE "id" = 1') }
197
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" WHERE "id" = 1)') }
198
+ end
199
+ end
@@ -0,0 +1,652 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Unary, '#visit_axiom_algebra_summarization' do
6
+ subject { object.visit_axiom_algebra_summarization(summarization) }
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', [ id ], [ [ 1 ] ]) }
16
+ let(:object) { described_class.new }
17
+
18
+ context 'summarize per table dee' do
19
+ let(:summarize_per) { TABLE_DEE }
20
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.age.count) } }
21
+
22
+ context 'when the operand is a base relation' do
23
+ let(:operand) { base_relation }
24
+
25
+ it_should_behave_like 'a generated SQL SELECT query'
26
+
27
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM "users"') }
28
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM "users")') }
29
+ end
30
+
31
+ context 'when the operand is an extension' do
32
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) } }
33
+
34
+ it_should_behave_like 'a generated SQL SELECT query'
35
+
36
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT *, 1 AS "one" FROM "users") AS "users"') }
37
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT *, 1 AS "one" FROM "users") AS "users")') }
38
+ end
39
+
40
+ context 'when the operand is a projection' do
41
+ let(:operand) { base_relation.project([ :id, :name ]) }
42
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
43
+
44
+ it_should_behave_like 'a generated SQL SELECT query'
45
+
46
+ its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM (SELECT DISTINCT "id", "name" FROM "users") AS "users"') }
47
+ its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM (SELECT DISTINCT "id", "name" FROM "users") AS "users")') }
48
+ end
49
+
50
+ context 'when the operand is a rename' do
51
+ let(:operand) { base_relation.rename(:name => :other_name) }
52
+
53
+ it_should_behave_like 'a generated SQL SELECT query'
54
+
55
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users"') }
56
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users")') }
57
+ end
58
+
59
+ context 'when the operand is a restriction' do
60
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
61
+
62
+ it_should_behave_like 'a generated SQL SELECT query'
63
+
64
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" WHERE "id" = 1) AS "users"') }
65
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" WHERE "id" = 1) AS "users")') }
66
+ end
67
+
68
+ context 'when the operand is a summarization' do
69
+ let(:operand) { base_relation.summarize([ :id ]) { |r| r.add(:count, r.age.count) } }
70
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.count.count) } }
71
+
72
+ it_should_behave_like 'a generated SQL SELECT query'
73
+
74
+ its(:to_s) { should eql('SELECT COUNT ("count") AS "count" FROM (SELECT "id", COUNT ("age") AS "count" FROM "users" GROUP BY "id" HAVING COUNT (*) > 0) AS "users"') }
75
+ its(:to_subquery) { should eql('(SELECT COUNT ("count") AS "count" FROM (SELECT "id", COUNT ("age") AS "count" FROM "users" GROUP BY "id" HAVING COUNT (*) > 0) AS "users")') }
76
+ end
77
+
78
+ context 'when the operand is ordered' do
79
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
80
+
81
+ it_should_behave_like 'a generated SQL SELECT query'
82
+
83
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users"') }
84
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users")') }
85
+ end
86
+
87
+ context 'when the operand is reversed' do
88
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
89
+
90
+ it_should_behave_like 'a generated SQL SELECT query'
91
+
92
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users"') }
93
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users")') }
94
+ end
95
+
96
+ context 'when the operand is limited' do
97
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
98
+
99
+ it_should_behave_like 'a generated SQL SELECT query'
100
+
101
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users"') }
102
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users")') }
103
+ end
104
+
105
+ context 'when the operand is an offset' do
106
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
107
+
108
+ it_should_behave_like 'a generated SQL SELECT query'
109
+
110
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users"') }
111
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users")') }
112
+ end
113
+
114
+ context 'when the operand is a difference' do
115
+ let(:operand) { base_relation.difference(base_relation) }
116
+
117
+ it_should_behave_like 'a generated SQL SELECT query'
118
+
119
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users"') }
120
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users")') }
121
+ end
122
+
123
+ context 'when the operand is an intersection' do
124
+ let(:operand) { base_relation.intersect(base_relation) }
125
+
126
+ it_should_behave_like 'a generated SQL SELECT query'
127
+
128
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users"') }
129
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users")') }
130
+ end
131
+
132
+ context 'when the operand is a union' do
133
+ let(:operand) { base_relation.union(base_relation) }
134
+
135
+ it_should_behave_like 'a generated SQL SELECT query'
136
+
137
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users"') }
138
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users")') }
139
+ end
140
+
141
+ context 'when the operand is a join' do
142
+ let(:operand) { base_relation.join(base_relation) }
143
+
144
+ it_should_behave_like 'a generated SQL SELECT query'
145
+
146
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users"') }
147
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users")') }
148
+ end
149
+ end
150
+
151
+ context 'summarize per table dum' do
152
+ let(:summarize_per) { TABLE_DUM }
153
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.age.count) } }
154
+
155
+ context 'when the operand is a base relation' do
156
+ let(:operand) { base_relation }
157
+
158
+ it_should_behave_like 'a generated SQL SELECT query'
159
+
160
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM "users" HAVING FALSE') }
161
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM "users" HAVING FALSE)') }
162
+ end
163
+
164
+ context 'when the operand is an extension' do
165
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) } }
166
+
167
+ it_should_behave_like 'a generated SQL SELECT query'
168
+
169
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT *, 1 AS "one" FROM "users") AS "users" HAVING FALSE') }
170
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT *, 1 AS "one" FROM "users") AS "users" HAVING FALSE)') }
171
+ end
172
+
173
+ context 'when the operand is a projection' do
174
+ let(:operand) { base_relation.project([ :id, :name ]) }
175
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
176
+
177
+ it_should_behave_like 'a generated SQL SELECT query'
178
+
179
+ its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM (SELECT DISTINCT "id", "name" FROM "users") AS "users" HAVING FALSE') }
180
+ its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM (SELECT DISTINCT "id", "name" FROM "users") AS "users" HAVING FALSE)') }
181
+ end
182
+
183
+ context 'when the operand is a rename' do
184
+ let(:operand) { base_relation.rename(:name => :other_name) }
185
+
186
+ it_should_behave_like 'a generated SQL SELECT query'
187
+
188
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users" HAVING FALSE') }
189
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users" HAVING FALSE)') }
190
+ end
191
+
192
+ context 'when the operand is a restriction' do
193
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
194
+
195
+ it_should_behave_like 'a generated SQL SELECT query'
196
+
197
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" WHERE "id" = 1) AS "users" HAVING FALSE') }
198
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" WHERE "id" = 1) AS "users" HAVING FALSE)') }
199
+ end
200
+
201
+ context 'when the operand is a summarization' do
202
+ let(:operand) { base_relation.summarize([ :id ]) { |r| r.add(:count, r.age.count) } }
203
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.count.count) } }
204
+
205
+ it_should_behave_like 'a generated SQL SELECT query'
206
+
207
+ its(:to_s) { should eql('SELECT COUNT ("count") AS "count" FROM (SELECT "id", COUNT ("age") AS "count" FROM "users" GROUP BY "id" HAVING COUNT (*) > 0) AS "users" HAVING FALSE') }
208
+ its(:to_subquery) { should eql('(SELECT COUNT ("count") AS "count" FROM (SELECT "id", COUNT ("age") AS "count" FROM "users" GROUP BY "id" HAVING COUNT (*) > 0) AS "users" HAVING FALSE)') }
209
+ end
210
+
211
+ context 'when the operand is ordered' do
212
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
213
+
214
+ it_should_behave_like 'a generated SQL SELECT query'
215
+
216
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" HAVING FALSE') }
217
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" HAVING FALSE)') }
218
+ end
219
+
220
+ context 'when the operand is reversed' do
221
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
222
+
223
+ it_should_behave_like 'a generated SQL SELECT query'
224
+
225
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users" HAVING FALSE') }
226
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users" HAVING FALSE)') }
227
+ end
228
+
229
+ context 'when the operand is limited' do
230
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
231
+
232
+ it_should_behave_like 'a generated SQL SELECT query'
233
+
234
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" HAVING FALSE') }
235
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" HAVING FALSE)') }
236
+ end
237
+
238
+ context 'when the operand is an offset' do
239
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
240
+
241
+ it_should_behave_like 'a generated SQL SELECT query'
242
+
243
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" HAVING FALSE') }
244
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" HAVING FALSE)') }
245
+ end
246
+
247
+ context 'when the operand is a difference' do
248
+ let(:operand) { base_relation.difference(base_relation) }
249
+
250
+ it_should_behave_like 'a generated SQL SELECT query'
251
+
252
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" HAVING FALSE') }
253
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" HAVING FALSE)') }
254
+ end
255
+
256
+ context 'when the operand is an intersection' do
257
+ let(:operand) { base_relation.intersect(base_relation) }
258
+
259
+ it_should_behave_like 'a generated SQL SELECT query'
260
+
261
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" HAVING FALSE') }
262
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" HAVING FALSE)') }
263
+ end
264
+
265
+ context 'when the operand is a union' do
266
+ let(:operand) { base_relation.union(base_relation) }
267
+
268
+ it_should_behave_like 'a generated SQL SELECT query'
269
+
270
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" HAVING FALSE') }
271
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" HAVING FALSE)') }
272
+ end
273
+
274
+ context 'when the operand is a join' do
275
+ let(:operand) { base_relation.join(base_relation) }
276
+
277
+ it_should_behave_like 'a generated SQL SELECT query'
278
+
279
+ its(:to_s) { should eql('SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" HAVING FALSE') }
280
+ its(:to_subquery) { should eql('(SELECT COUNT ("age") AS "count" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" HAVING FALSE)') }
281
+ end
282
+ end
283
+
284
+ context 'summarize by a subset of the operand header' do
285
+ let(:summarize_by) { [ :id ] }
286
+ let(:summarization) { operand.summarize(summarize_by) { |r| r.add(:count, r.age.count) } }
287
+
288
+ context 'when the operand is a base relation' do
289
+ let(:operand) { base_relation }
290
+
291
+ it_should_behave_like 'a generated SQL SELECT query'
292
+
293
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "users" GROUP BY "id" HAVING COUNT (*) > 0') }
294
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
295
+ end
296
+
297
+ context 'when the operand is a projection' do
298
+ let(:operand) { base_relation.project([ :id, :name ]) }
299
+ let(:summarization) { operand.summarize(summarize_by) { |r| r.add(:count, r.name.count) } }
300
+
301
+ it_should_behave_like 'a generated SQL SELECT query'
302
+
303
+ its(:to_s) { should eql('SELECT "id", COUNT ("name") AS "count" FROM (SELECT DISTINCT "id", "name" FROM "users") AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
304
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("name") AS "count" FROM (SELECT DISTINCT "id", "name" FROM "users") AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
305
+ end
306
+
307
+ context 'when the operand is an extension' do
308
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) } }
309
+
310
+ it_should_behave_like 'a generated SQL SELECT query'
311
+
312
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT *, 1 AS "one" FROM "users") AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
313
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT *, 1 AS "one" FROM "users") AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
314
+ end
315
+
316
+ context 'when the operand is a rename' do
317
+ let(:operand) { base_relation.rename(:name => :other_name) }
318
+
319
+ it_should_behave_like 'a generated SQL SELECT query'
320
+
321
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
322
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
323
+ end
324
+
325
+ context 'when the operand is a restriction' do
326
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
327
+
328
+ it_should_behave_like 'a generated SQL SELECT query'
329
+
330
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" WHERE "id" = 1) AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
331
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" WHERE "id" = 1) AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
332
+ end
333
+
334
+ context 'when the operand is ordered' do
335
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
336
+
337
+ it_should_behave_like 'a generated SQL SELECT query'
338
+
339
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
340
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
341
+ end
342
+
343
+ context 'when the operand is reversed' do
344
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
345
+
346
+ it_should_behave_like 'a generated SQL SELECT query'
347
+
348
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
349
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
350
+ end
351
+
352
+ context 'when the operand is limited' do
353
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
354
+
355
+ it_should_behave_like 'a generated SQL SELECT query'
356
+
357
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
358
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
359
+ end
360
+
361
+ context 'when the operand is an offset' do
362
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
363
+
364
+ it_should_behave_like 'a generated SQL SELECT query'
365
+
366
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
367
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
368
+ end
369
+
370
+ context 'when the operand is a difference' do
371
+ let(:operand) { base_relation.difference(base_relation) }
372
+
373
+ it_should_behave_like 'a generated SQL SELECT query'
374
+
375
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
376
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
377
+ end
378
+
379
+ context 'when the operand is an intersection' do
380
+ let(:operand) { base_relation.intersect(base_relation) }
381
+
382
+ it_should_behave_like 'a generated SQL SELECT query'
383
+
384
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
385
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
386
+ end
387
+
388
+ context 'when the operand is a union' do
389
+ let(:operand) { base_relation.union(base_relation) }
390
+
391
+ it_should_behave_like 'a generated SQL SELECT query'
392
+
393
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
394
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
395
+ end
396
+
397
+ context 'when the operand is a join' do
398
+ let(:operand) { base_relation.join(base_relation) }
399
+
400
+ it_should_behave_like 'a generated SQL SELECT query'
401
+
402
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" GROUP BY "id" HAVING COUNT (*) > 0') }
403
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" GROUP BY "id" HAVING COUNT (*) > 0)') }
404
+ end
405
+ end
406
+
407
+ context 'summarize per another base relation' do
408
+ let(:summarize_per) { other_relation }
409
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.age.count) } }
410
+
411
+ context 'when the operand is a base relation' do
412
+ let(:operand) { base_relation }
413
+
414
+ it_should_behave_like 'a generated SQL SELECT query'
415
+
416
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN "users" GROUP BY "id"') }
417
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN "users" GROUP BY "id")') }
418
+ end
419
+
420
+ context 'when the operand is a projection' do
421
+ let(:operand) { base_relation.project([ :id, :name ]) }
422
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.name.count) } }
423
+
424
+ it_should_behave_like 'a generated SQL SELECT query'
425
+
426
+ its(:to_s) { should eql('SELECT "id", COUNT ("name") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT DISTINCT "id", "name" FROM "users") AS "users" GROUP BY "id"') }
427
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("name") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT DISTINCT "id", "name" FROM "users") AS "users" GROUP BY "id")') }
428
+ end
429
+
430
+ context 'when the operand is an extension' do
431
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) } }
432
+
433
+ it_should_behave_like 'a generated SQL SELECT query'
434
+
435
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT *, 1 AS "one" FROM "users") AS "users" GROUP BY "id"') }
436
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT *, 1 AS "one" FROM "users") AS "users" GROUP BY "id")') }
437
+ end
438
+
439
+ context 'when the operand is a rename' do
440
+ let(:operand) { base_relation.rename(:name => :other_name) }
441
+
442
+ it_should_behave_like 'a generated SQL SELECT query'
443
+
444
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users" GROUP BY "id"') }
445
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users" GROUP BY "id")') }
446
+ end
447
+
448
+ context 'when the operand is a restriction' do
449
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
450
+
451
+ it_should_behave_like 'a generated SQL SELECT query'
452
+
453
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" WHERE "id" = 1) AS "users" GROUP BY "id"') }
454
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" WHERE "id" = 1) AS "users" GROUP BY "id")') }
455
+ end
456
+
457
+ context 'when the operand is ordered' do
458
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
459
+
460
+ it_should_behave_like 'a generated SQL SELECT query'
461
+
462
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" GROUP BY "id"') }
463
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" GROUP BY "id")') }
464
+ end
465
+
466
+ context 'when the operand is reversed' do
467
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
468
+
469
+ it_should_behave_like 'a generated SQL SELECT query'
470
+
471
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users" GROUP BY "id"') }
472
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users" GROUP BY "id")') }
473
+ end
474
+
475
+ context 'when the operand is limited' do
476
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
477
+
478
+ it_should_behave_like 'a generated SQL SELECT query'
479
+
480
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" GROUP BY "id"') }
481
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" GROUP BY "id")') }
482
+ end
483
+
484
+ context 'when the operand is an offset' do
485
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
486
+
487
+ it_should_behave_like 'a generated SQL SELECT query'
488
+
489
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" GROUP BY "id"') }
490
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" GROUP BY "id")') }
491
+ end
492
+
493
+ context 'when the operand is a difference' do
494
+ let(:operand) { base_relation.difference(base_relation) }
495
+
496
+ it_should_behave_like 'a generated SQL SELECT query'
497
+
498
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id"') }
499
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id")') }
500
+ end
501
+
502
+ context 'when the operand is an intersection' do
503
+ let(:operand) { base_relation.intersect(base_relation) }
504
+
505
+ it_should_behave_like 'a generated SQL SELECT query'
506
+
507
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id"') }
508
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id")') }
509
+ end
510
+
511
+ context 'when the operand is a union' do
512
+ let(:operand) { base_relation.union(base_relation) }
513
+
514
+ it_should_behave_like 'a generated SQL SELECT query'
515
+
516
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id"') }
517
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id")') }
518
+ end
519
+
520
+ context 'when the operand is a join' do
521
+ let(:operand) { base_relation.join(base_relation) }
522
+
523
+ it_should_behave_like 'a generated SQL SELECT query'
524
+
525
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" GROUP BY "id"') }
526
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM "other" AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" GROUP BY "id")') }
527
+ end
528
+ end
529
+
530
+ context 'summarize per another projected relation' do
531
+ let(:summarize_per) { other_relation.project([ :id ]) }
532
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.age.count) } }
533
+
534
+ context 'when the operand is a base relation' do
535
+ let(:operand) { base_relation }
536
+
537
+ it_should_behave_like 'a generated SQL SELECT query'
538
+
539
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN "users" GROUP BY "id"') }
540
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN "users" GROUP BY "id")') }
541
+ end
542
+
543
+ context 'when the operand is a projection' do
544
+ let(:operand) { base_relation.project([ :id, :name ]) }
545
+ let(:summarization) { operand.summarize(summarize_per) { |r| r.add(:count, r.name.count) } }
546
+
547
+ it_should_behave_like 'a generated SQL SELECT query'
548
+
549
+ its(:to_s) { should eql('SELECT "id", COUNT ("name") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT DISTINCT "id", "name" FROM "users") AS "users" GROUP BY "id"') }
550
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("name") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT DISTINCT "id", "name" FROM "users") AS "users" GROUP BY "id")') }
551
+ end
552
+
553
+ context 'when the operand is an extension' do
554
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) } }
555
+
556
+ it_should_behave_like 'a generated SQL SELECT query'
557
+
558
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT *, 1 AS "one" FROM "users") AS "users" GROUP BY "id"') }
559
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT *, 1 AS "one" FROM "users") AS "users" GROUP BY "id")') }
560
+ end
561
+
562
+ context 'when the operand is a rename' do
563
+ let(:operand) { base_relation.rename(:name => :other_name) }
564
+
565
+ it_should_behave_like 'a generated SQL SELECT query'
566
+
567
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users" GROUP BY "id"') }
568
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users" GROUP BY "id")') }
569
+ end
570
+
571
+ context 'when the operand is a restriction' do
572
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
573
+
574
+ it_should_behave_like 'a generated SQL SELECT query'
575
+
576
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" WHERE "id" = 1) AS "users" GROUP BY "id"') }
577
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" WHERE "id" = 1) AS "users" GROUP BY "id")') }
578
+ end
579
+
580
+ context 'when the operand is ordered' do
581
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
582
+
583
+ it_should_behave_like 'a generated SQL SELECT query'
584
+
585
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" GROUP BY "id"') }
586
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" GROUP BY "id")') }
587
+ end
588
+
589
+ context 'when the operand is reversed' do
590
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
591
+
592
+ it_should_behave_like 'a generated SQL SELECT query'
593
+
594
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users" GROUP BY "id"') }
595
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) AS "users" GROUP BY "id")') }
596
+ end
597
+
598
+ context 'when the operand is limited' do
599
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
600
+
601
+ it_should_behave_like 'a generated SQL SELECT query'
602
+
603
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" GROUP BY "id"') }
604
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" GROUP BY "id")') }
605
+ end
606
+
607
+ context 'when the operand is an offset' do
608
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
609
+
610
+ it_should_behave_like 'a generated SQL SELECT query'
611
+
612
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" GROUP BY "id"') }
613
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" GROUP BY "id")') }
614
+ end
615
+
616
+ context 'when the operand is a difference' do
617
+ let(:operand) { base_relation.difference(base_relation) }
618
+
619
+ it_should_behave_like 'a generated SQL SELECT query'
620
+
621
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id"') }
622
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id")') }
623
+ end
624
+
625
+ context 'when the operand is an intersection' do
626
+ let(:operand) { base_relation.intersect(base_relation) }
627
+
628
+ it_should_behave_like 'a generated SQL SELECT query'
629
+
630
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id"') }
631
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id")') }
632
+ end
633
+
634
+ context 'when the operand is a union' do
635
+ let(:operand) { base_relation.union(base_relation) }
636
+
637
+ it_should_behave_like 'a generated SQL SELECT query'
638
+
639
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id"') }
640
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" GROUP BY "id")') }
641
+ end
642
+
643
+ context 'when the operand is a join' do
644
+ let(:operand) { base_relation.join(base_relation) }
645
+
646
+ it_should_behave_like 'a generated SQL SELECT query'
647
+
648
+ its(:to_s) { should eql('SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" GROUP BY "id"') }
649
+ its(:to_subquery) { should eql('(SELECT "id", COUNT ("age") AS "count" FROM (SELECT DISTINCT "id" FROM "other") AS "other" NATURAL LEFT JOIN (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" GROUP BY "id")') }
650
+ end
651
+ end
652
+ end