axiom-sql-generator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Insertion, '#to_subquery' do
6
+ subject { object.to_subquery }
7
+
8
+ let(:object) { described_class.new }
9
+
10
+ it 'delegates to #to_s' do
11
+ sql = stub('sql')
12
+ object.should_receive(:to_s).with(no_args).and_return(sql)
13
+ should equal(sql)
14
+ end
15
+ end
@@ -0,0 +1,187 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Insertion, '#visit_axiom_relation_operation_insertion' do
6
+ subject { object.visit_axiom_relation_operation_insertion(insertion) }
7
+
8
+ let(:object) { described_class.new }
9
+ let(:insertion) { operand.insert(other) }
10
+ let(:operand) { Relation::Base.new(relation_name, header, body) }
11
+ let(:relation_name) { 'users' }
12
+ let(:header) { [ id, name, age ] }
13
+ let(:body) { [].each }
14
+ let(:id) { Attribute::Integer.new(:id) }
15
+ let(:name) { Attribute::String.new(:name) }
16
+ let(:age) { Attribute::Integer.new(:age, :required => false) }
17
+
18
+ context 'inserting a non-empty materialized relation' do
19
+ let(:other) { operand.materialize }
20
+ let(:body) { [ [ 1, 'Dan Kubb', 36 ] ].each }
21
+
22
+ it_should_behave_like 'a generated SQL expression'
23
+
24
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") VALUES (1, \'Dan Kubb\', 36)') }
25
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") VALUES (1, \'Dan Kubb\', 36)') }
26
+ end
27
+
28
+ context 'inserting an empty materialized relation' do
29
+ let(:other) { operand.materialize }
30
+
31
+ it_should_behave_like 'a generated SQL expression'
32
+
33
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT 0 LIMIT 0') }
34
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT 0 LIMIT 0') }
35
+ end
36
+
37
+ context 'inserting a base relation' do
38
+ let(:other) { operand }
39
+
40
+ it_should_behave_like 'a generated SQL expression'
41
+
42
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users"') }
43
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users"') }
44
+ end
45
+
46
+ context 'inserting a projection' do
47
+ let(:header) { [ id, name ] }
48
+ let(:other) { operand.project([ :id, :name ]) }
49
+
50
+ it_should_behave_like 'a generated SQL SELECT query'
51
+
52
+ its(:to_s) { should eql('INSERT INTO users ("id", "name") SELECT DISTINCT "id", "name" FROM "users"') }
53
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name") SELECT DISTINCT "id", "name" FROM "users"') }
54
+ end
55
+
56
+ context 'inserting an extension' do
57
+ let(:other) { Relation::Base.new('other', [ id, name ], body).extend { |r| r.add(age, 1) } }
58
+
59
+ it_should_behave_like 'a generated SQL SELECT query'
60
+
61
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", 1 AS "age" FROM "other"') }
62
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", 1 AS "age" FROM "other"') }
63
+ end
64
+
65
+ context 'inserting a rename' do
66
+ let(:other) { Relation::Base.new('other', [ [ :other_id, Integer ], name, age ], body).rename(:other_id => :id) }
67
+
68
+ it_should_behave_like 'a generated SQL SELECT query'
69
+
70
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "other_id" AS "id", "name", "age" FROM "other"') }
71
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "other_id" AS "id", "name", "age" FROM "other"') }
72
+ end
73
+
74
+ context 'inserting a restriction' do
75
+ let(:other) { operand.restrict { |r| r.id.eq(1) } }
76
+
77
+ it_should_behave_like 'a generated SQL SELECT query'
78
+
79
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" WHERE "id" = 1') }
80
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" WHERE "id" = 1') }
81
+ end
82
+
83
+ context 'inserting a summarization' do
84
+ context 'summarize per table dee' do
85
+ let(:summarize_per) { TABLE_DEE }
86
+ let(:operand) { Relation::Base.new(relation_name, [ id ], body) }
87
+ let(:other) { operand.summarize(summarize_per) { |r| r.add(id, r.id.count) } }
88
+
89
+ it_should_behave_like 'a generated SQL SELECT query'
90
+
91
+ its(:to_s) { should eql('INSERT INTO users ("id") SELECT COUNT ("id") AS "id" FROM "users"') }
92
+ its(:to_subquery) { should eql('INSERT INTO users ("id") SELECT COUNT ("id") AS "id" FROM "users"') }
93
+ end
94
+
95
+ context 'summarize per table dum' do
96
+ let(:summarize_per) { TABLE_DUM }
97
+ let(:operand) { Relation::Base.new(relation_name, [ id ], body) }
98
+ let(:other) { operand.summarize(summarize_per) { |r| r.add(id, r.id.count) } }
99
+
100
+ it_should_behave_like 'a generated SQL SELECT query'
101
+
102
+ its(:to_s) { should eql('INSERT INTO users ("id") SELECT COUNT ("id") AS "id" FROM "users" HAVING FALSE') }
103
+ its(:to_subquery) { should eql('INSERT INTO users ("id") SELECT COUNT ("id") AS "id" FROM "users" HAVING FALSE') }
104
+ end
105
+
106
+ context 'summarize by a subset of the operand header' do
107
+ let(:other) { operand.summarize([ :id, :name ]) { |r| r.add(age, r.age.count) } }
108
+
109
+ it_should_behave_like 'a generated SQL SELECT query'
110
+
111
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", COUNT ("age") AS "age" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0') }
112
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", COUNT ("age") AS "age" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0') }
113
+ end
114
+ end
115
+
116
+ context 'inserting an order' do
117
+ let(:other) { operand.sort_by { |r| [ r.id, r.name, r.age ] } }
118
+
119
+ it_should_behave_like 'a generated SQL SELECT query'
120
+
121
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age"') }
122
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age"') }
123
+ end
124
+
125
+ context 'inserting a reverse' do
126
+ let(:other) { operand.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
127
+
128
+ it_should_behave_like 'a generated SQL SELECT query'
129
+
130
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') }
131
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') }
132
+ end
133
+
134
+ context 'inserting a limit' do
135
+ let(:other) { operand.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
136
+
137
+ it_should_behave_like 'a generated SQL SELECT query'
138
+
139
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') }
140
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') }
141
+ end
142
+
143
+ context 'inserting an offset' do
144
+ let(:other) { operand.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
145
+
146
+ it_should_behave_like 'a generated SQL SELECT query'
147
+
148
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') }
149
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') }
150
+ end
151
+
152
+ context 'inserting a difference' do
153
+ let(:other) { operand.difference(operand) }
154
+
155
+ it_should_behave_like 'a generated SQL SELECT query'
156
+
157
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") (SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")') }
158
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") (SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")') }
159
+ end
160
+
161
+ context 'inserting a intersection' do
162
+ let(:other) { operand.intersect(operand) }
163
+
164
+ it_should_behave_like 'a generated SQL SELECT query'
165
+
166
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") (SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")') }
167
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") (SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")') }
168
+ end
169
+
170
+ context 'inserting a union' do
171
+ let(:other) { operand.union(operand) }
172
+
173
+ it_should_behave_like 'a generated SQL SELECT query'
174
+
175
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") (SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")') }
176
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") (SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")') }
177
+ end
178
+
179
+ context 'inserting a join' do
180
+ let(:other) { operand.join(operand) }
181
+
182
+ it_should_behave_like 'a generated SQL SELECT query'
183
+
184
+ its(:to_s) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "users" AS "right"') }
185
+ its(:to_subquery) { should eql('INSERT INTO users ("id", "name", "age") SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "users" AS "right"') }
186
+ end
187
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Materialized, '#visit_axiom_relation_materialized' do
6
+ subject { object.visit_axiom_relation_materialized(relation) }
7
+
8
+ let(:object) { described_class.new }
9
+ let(:header) { [ [ :id, Integer ], [ :name, String ] ] }
10
+
11
+ context 'with an non-empty relation' do
12
+ let(:relation) { Relation.new(header, [ [ 1, 'John Doe' ], [ 2, 'Jane Doe' ] ]) }
13
+
14
+ it_should_behave_like 'a generated SQL expression'
15
+
16
+ its(:to_s) { should eql('VALUES (1, \'John Doe\'), (2, \'Jane Doe\')') }
17
+ its(:to_subquery) { should eql('(VALUES (1, \'John Doe\'), (2, \'Jane Doe\'))') }
18
+ end
19
+
20
+ context 'with an empty relation' do
21
+ let(:relation) { Relation.new(header, []) }
22
+
23
+ it_should_behave_like 'a generated SQL expression'
24
+
25
+ its(:to_s) { should eql('SELECT 0 LIMIT 0') }
26
+ its(:to_subquery) { should eql('(SELECT 0 LIMIT 0)') }
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Materialized, '#visited?' do
6
+ subject { object.visited? }
7
+
8
+ let(:object) { described_class.new }
9
+ let(:relation) { Relation.new([ [ :id, Integer ] ], [ [] ]) }
10
+
11
+ context 'when the relation is visited' do
12
+ before do
13
+ object.visit_axiom_relation_materialized(relation)
14
+ end
15
+
16
+ it_should_behave_like 'an idempotent method'
17
+
18
+ it { should be(true) }
19
+ end
20
+
21
+ context 'when the relation is not visited' do
22
+ it_should_behave_like 'an idempotent method'
23
+
24
+ it { should be(false) }
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation, '#name' do
6
+ subject { object.name }
7
+
8
+ let(:described_class) { Class.new(SQL::Generator::Relation) }
9
+ let(:object) { described_class.new }
10
+
11
+ context 'when name is nil' do
12
+ it_should_behave_like 'an idempotent method'
13
+
14
+ it { should be_nil }
15
+ end
16
+
17
+ context 'when name is set' do
18
+ let(:name) { 'test' }
19
+
20
+ before do
21
+ # subclasses set @name, but nothing in this class
22
+ # does does so simulate it being set
23
+ object.instance_variable_set(:@name, name)
24
+ end
25
+
26
+ it_should_behave_like 'an idempotent method'
27
+
28
+ it { should equal(name) }
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Set, '.normalize_operand_headers' do
6
+ subject { object.normalize_operand_headers(relation) }
7
+
8
+ let(:object) { described_class }
9
+ let(:relation) { left.union(right) }
10
+ let(:relation_name) { 'test' }
11
+ let(:header) { [ [ :id, Integer, ], [ :name, String ] ] }
12
+ let(:body) { [].each }
13
+
14
+ context 'when the left and right headers are sorted in the same order' do
15
+ let(:left) { Relation::Base.new(relation_name, header, body) }
16
+ let(:right) { Relation::Base.new(relation_name, header, body) }
17
+
18
+ it { should equal(relation) }
19
+ end
20
+
21
+ context 'when the left and right headers are sorted in different order' do
22
+ let(:left) { Relation::Base.new(relation_name, header, body) }
23
+ let(:right) { Relation::Base.new(relation_name, header.reverse, body) }
24
+
25
+ it { should_not equal(relation) }
26
+
27
+ its(:right) { should_not equal(right) }
28
+
29
+ it { should be_kind_of(relation.class) }
30
+
31
+ its(:left) { should equal(left) }
32
+
33
+ its(:right) { should eql(right.project(header)) }
34
+ end
35
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Set, '#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 difference is visited' do
27
+ before do
28
+ object.visit(base_relation.difference(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") EXCEPT (SELECT "id", "name", "age" FROM "users")') }
34
+ end
35
+
36
+ context 'when an intersection is visited' do
37
+ before do
38
+ object.visit(base_relation.intersect(base_relation))
39
+ end
40
+
41
+ it_should_behave_like 'a generated SQL expression'
42
+
43
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")') }
44
+ end
45
+
46
+ context 'when a union is visited' do
47
+ before do
48
+ object.visit(base_relation.union(base_relation))
49
+ end
50
+
51
+ it_should_behave_like 'a generated SQL expression'
52
+
53
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")') }
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Set, '#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 difference is visited' do
27
+ before do
28
+ object.visit(base_relation.difference(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") EXCEPT (SELECT "id", "name", "age" FROM "users"))') }
34
+ end
35
+
36
+ context 'when an intersection is visited' do
37
+ before do
38
+ object.visit(base_relation.intersect(base_relation))
39
+ end
40
+
41
+ it_should_behave_like 'a generated SQL expression'
42
+
43
+ its(:to_s) { should eql('((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users"))') }
44
+ end
45
+
46
+ context 'when a union is visited' do
47
+ before do
48
+ object.visit(base_relation.union(base_relation))
49
+ end
50
+
51
+ it_should_behave_like 'a generated SQL expression'
52
+
53
+ its(:to_s) { should eql('((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users"))') }
54
+ end
55
+ end
@@ -0,0 +1,191 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Set, '#visit_axiom_algebra_difference' do
6
+ subject { object.visit_axiom_algebra_difference(difference) }
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(:left) { operand }
16
+ let(:right) { operand }
17
+ let(:difference) { left.difference(right) }
18
+ let(:object) { described_class.new }
19
+
20
+ context 'when the operands are base relations' do
21
+ let(:operand) { base_relation }
22
+
23
+ it_should_behave_like 'a generated SQL SELECT query'
24
+
25
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")') }
26
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users"))') }
27
+ end
28
+
29
+ context 'when the operands are projections' do
30
+ let(:operand) { base_relation.project([ :id, :name ]) }
31
+
32
+ it_should_behave_like 'a generated SQL SELECT query'
33
+
34
+ its(:to_s) { should eql('(SELECT DISTINCT "id", "name" FROM "users") EXCEPT (SELECT DISTINCT "id", "name" FROM "users")') }
35
+ its(:to_subquery) { should eql('((SELECT DISTINCT "id", "name" FROM "users") EXCEPT (SELECT DISTINCT "id", "name" FROM "users"))') }
36
+ end
37
+
38
+ context 'when the operand is an extension' do
39
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) } }
40
+
41
+ it_should_behave_like 'a generated SQL SELECT query'
42
+
43
+ its(:to_s) { should eql('(SELECT "id", "name", "age", 1 AS "one" FROM "users") EXCEPT (SELECT "id", "name", "age", 1 AS "one" FROM "users")') }
44
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age", 1 AS "one" FROM "users") EXCEPT (SELECT "id", "name", "age", 1 AS "one" FROM "users"))') }
45
+ end
46
+
47
+ context 'when the operands are renames' do
48
+ let(:operand) { base_relation.rename(:id => :user_id) }
49
+
50
+ it_should_behave_like 'a generated SQL SELECT query'
51
+
52
+ its(:to_s) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users") EXCEPT (SELECT "id" AS "user_id", "name", "age" FROM "users")') }
53
+ its(:to_subquery) { should eql('((SELECT "id" AS "user_id", "name", "age" FROM "users") EXCEPT (SELECT "id" AS "user_id", "name", "age" FROM "users"))') }
54
+ end
55
+
56
+ context 'when the operands are restrictions' do
57
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
58
+
59
+ it_should_behave_like 'a generated SQL SELECT query'
60
+
61
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users" WHERE "id" = 1) EXCEPT (SELECT "id", "name", "age" FROM "users" WHERE "id" = 1)') }
62
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age" FROM "users" WHERE "id" = 1) EXCEPT (SELECT "id", "name", "age" FROM "users" WHERE "id" = 1))') }
63
+ end
64
+
65
+ context 'when the operand is a summarization' do
66
+ end
67
+
68
+ context 'when the operand is a summarization' do
69
+ context 'summarize per table dee' do
70
+ let(:summarize_per) { TABLE_DEE }
71
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
72
+
73
+ it_should_behave_like 'a generated SQL SELECT query'
74
+
75
+ its(:to_s) { should eql('(SELECT COUNT ("id") AS "count" FROM "users") EXCEPT (SELECT COUNT ("id") AS "count" FROM "users")') }
76
+ its(:to_subquery) { should eql('((SELECT COUNT ("id") AS "count" FROM "users") EXCEPT (SELECT COUNT ("id") AS "count" FROM "users"))') }
77
+ end
78
+
79
+ context 'summarize per table dum' do
80
+ let(:summarize_per) { TABLE_DUM }
81
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } }
82
+
83
+ it_should_behave_like 'a generated SQL SELECT query'
84
+
85
+ its(:to_s) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) EXCEPT (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE)') }
86
+ its(:to_subquery) { should eql('((SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) EXCEPT (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE))') }
87
+ end
88
+
89
+ context 'summarize by a subset of the operand header' do
90
+ let(:operand) { base_relation.summarize([ :id, :name ]) { |r| r.add(:count, r.age.count) } }
91
+
92
+ it_should_behave_like 'a generated SQL SELECT query'
93
+
94
+ its(:to_s) { should eql('(SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) EXCEPT (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0)') }
95
+ its(:to_subquery) { should eql('((SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) EXCEPT (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0))') }
96
+ end
97
+ end
98
+
99
+ context 'when the operand is ordered' do
100
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
101
+
102
+ it_should_behave_like 'a generated SQL SELECT query'
103
+
104
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age") EXCEPT (SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age")') }
105
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age") EXCEPT (SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age"))') }
106
+ end
107
+
108
+ context 'when the operand is reversed' do
109
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
110
+
111
+ it_should_behave_like 'a generated SQL SELECT query'
112
+
113
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) EXCEPT (SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') }
114
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC) EXCEPT (SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC))') }
115
+ end
116
+
117
+ context 'when the operand is limited' do
118
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
119
+
120
+ it_should_behave_like 'a generated SQL SELECT query'
121
+
122
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1) EXCEPT (SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') }
123
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1) EXCEPT (SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1))') }
124
+ end
125
+
126
+ context 'when the operands are offsets' do
127
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
128
+
129
+ it_should_behave_like 'a generated SQL SELECT query'
130
+
131
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1) EXCEPT (SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') }
132
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1) EXCEPT (SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1))') }
133
+ end
134
+
135
+ context 'when the operands are differences' do
136
+ let(:operand) { base_relation.difference(base_relation) }
137
+
138
+ it_should_behave_like 'a generated SQL SELECT query'
139
+
140
+ its(:to_s) { should eql('((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) EXCEPT ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users"))') }
141
+ its(:to_subquery) { should eql('(((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) EXCEPT ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")))') }
142
+ end
143
+
144
+ context 'when the operands are intersections' do
145
+ let(:operand) { base_relation.intersect(base_relation) }
146
+
147
+ it_should_behave_like 'a generated SQL SELECT query'
148
+
149
+ its(:to_s) { should eql('((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) EXCEPT ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users"))') }
150
+ its(:to_subquery) { should eql('(((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) EXCEPT ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")))') }
151
+ end
152
+
153
+ context 'when the operands are unions' do
154
+ let(:operand) { base_relation.union(base_relation) }
155
+
156
+ it_should_behave_like 'a generated SQL SELECT query'
157
+
158
+ its(:to_s) { should eql('((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) EXCEPT ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users"))') }
159
+ its(:to_subquery) { should eql('(((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) EXCEPT ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")))') }
160
+ end
161
+
162
+ context 'when the operands are joins' do
163
+ let(:operand) { base_relation.join(base_relation) }
164
+
165
+ it_should_behave_like 'a generated SQL SELECT query'
166
+
167
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "users" AS "right") EXCEPT (SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "users" AS "right")') }
168
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "users" AS "right") EXCEPT (SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "users" AS "right"))') }
169
+ end
170
+
171
+ context 'when the operands have different base relations' do
172
+ let(:relation_name) { 'users_others' }
173
+ let(:left) { Relation::Base.new('users', header, body) }
174
+ let(:right) { Relation::Base.new('others', header, body) }
175
+
176
+ it_should_behave_like 'a generated SQL SELECT query'
177
+
178
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "others")') }
179
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "others"))') }
180
+ end
181
+
182
+ context 'when the operands have headers sorted in different orders' do
183
+ let(:left) { Relation::Base.new(relation_name, header, body) }
184
+ let(:right) { Relation::Base.new(relation_name, header.reverse, body) }
185
+
186
+ it_should_behave_like 'a generated SQL SELECT query'
187
+
188
+ its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT DISTINCT "id", "name", "age" FROM "users")') }
189
+ its(:to_subquery) { should eql('((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT DISTINCT "id", "name", "age" FROM "users"))') }
190
+ end
191
+ end