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,21 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Unary, '#visit_axiom_relation_base' do
6
+ subject { object.visit_axiom_relation_base(base_relation) }
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(:object) { described_class.new }
16
+
17
+ it_should_behave_like 'a generated SQL SELECT query'
18
+
19
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users"') }
20
+ its(:to_subquery) { should eql('(SELECT * FROM "users")') }
21
+ end
@@ -0,0 +1,165 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Unary, '#visit_axiom_relation_operation_limit' do
6
+ subject { object.visit_axiom_relation_operation_limit(limit) }
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(:limit) { operand.take(1) }
16
+ let(:object) { described_class.new }
17
+
18
+ context 'when the operand is a base relation' do
19
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
20
+
21
+ it_should_behave_like 'a generated SQL SELECT query'
22
+
23
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') }
24
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') }
25
+ end
26
+
27
+ context 'when the operand is a projection' do
28
+ let(:operand) { base_relation.project([ :id, :name ]).sort_by { |r| [ r.id, r.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" ORDER BY "id", "name" LIMIT 1') }
33
+ its(:to_subquery) { should eql('(SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id", "name" LIMIT 1)') }
34
+ end
35
+
36
+ context 'when the operand is an extension' do
37
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) }.sort_by { |r| [ r.id, r.name, r.age, r.one ] } }
38
+
39
+ it_should_behave_like 'a generated SQL SELECT query'
40
+
41
+ its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one" LIMIT 1') }
42
+ its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one" LIMIT 1)') }
43
+ end
44
+
45
+ context 'when the operand is a rename' do
46
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.rename(:id => :user_id) }
47
+
48
+ it_should_behave_like 'a generated SQL SELECT query'
49
+
50
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') }
51
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') }
52
+ end
53
+
54
+ context 'when the operand is a restriction' do
55
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) }.sort_by { |r| [ r.id, r.name, r.age ] } }
56
+
57
+ it_should_behave_like 'a generated SQL SELECT query'
58
+
59
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age" LIMIT 1') }
60
+ its(:to_subquery) { should eql('(SELECT * FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age" LIMIT 1)') }
61
+ end
62
+
63
+ context 'when the operand is a summarization' do
64
+ context 'summarize per table dee' do
65
+ let(:summarize_per) { TABLE_DEE }
66
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } }
67
+
68
+ it_should_behave_like 'a generated SQL SELECT query'
69
+
70
+ its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" LIMIT 1') }
71
+ its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" LIMIT 1)') }
72
+ end
73
+
74
+ context 'summarize per table dum' do
75
+ let(:summarize_per) { TABLE_DUM }
76
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } }
77
+
78
+ it_should_behave_like 'a generated SQL SELECT query'
79
+
80
+ its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" LIMIT 1') }
81
+ its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" LIMIT 1)') }
82
+ end
83
+
84
+ context 'summarize by a subset of the operand header' do
85
+ let(:operand) { base_relation.summarize([ :id, :name ]) { |r| r.add(:count, r.age.count) }.sort_by { |r| [ r.id, r.name, r.count ] } }
86
+
87
+ it_should_behave_like 'a generated SQL SELECT query'
88
+
89
+ its(:to_s) { should eql('SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count" LIMIT 1') }
90
+ its(:to_subquery) { should eql('(SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count" LIMIT 1)') }
91
+ end
92
+ end
93
+
94
+ context 'when the operand is ordered' do
95
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
96
+
97
+ it_should_behave_like 'a generated SQL SELECT query'
98
+
99
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') }
100
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') }
101
+ end
102
+
103
+ context 'when the operand is reversed' do
104
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
105
+
106
+ it_should_behave_like 'a generated SQL SELECT query'
107
+
108
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC LIMIT 1') }
109
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC LIMIT 1)') }
110
+ end
111
+
112
+ context 'when the operand is limited' do
113
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
114
+
115
+ it_should_behave_like 'a generated SQL SELECT query'
116
+
117
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" LIMIT 1') }
118
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" LIMIT 1)') }
119
+ end
120
+
121
+ context 'when the operand is an offset' do
122
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
123
+
124
+ it_should_behave_like 'a generated SQL SELECT query'
125
+
126
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1 OFFSET 1') }
127
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1 OFFSET 1)') }
128
+ end
129
+
130
+ context 'when the operand is a difference' do
131
+ let(:operand) { base_relation.difference(base_relation).sort_by { |r| [ r.id, r.name, r.age ] } }
132
+
133
+ it_should_behave_like 'a generated SQL SELECT query'
134
+
135
+ 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" ORDER BY "id", "name", "age" LIMIT 1') }
136
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" LIMIT 1)') }
137
+ end
138
+
139
+ context 'when the operand is an intersection' do
140
+ let(:operand) { base_relation.intersect(base_relation).sort_by { |r| [ r.id, r.name, r.age ] } }
141
+
142
+ it_should_behave_like 'a generated SQL SELECT query'
143
+
144
+ 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" ORDER BY "id", "name", "age" LIMIT 1') }
145
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" LIMIT 1)') }
146
+ end
147
+
148
+ context 'when the operand is a union' do
149
+ let(:operand) { base_relation.union(base_relation).sort_by { |r| [ r.id, r.name, r.age ] } }
150
+
151
+ it_should_behave_like 'a generated SQL SELECT query'
152
+
153
+ 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" ORDER BY "id", "name", "age" LIMIT 1') }
154
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" LIMIT 1)') }
155
+ end
156
+
157
+ context 'when the operand is a join' do
158
+ let(:operand) { base_relation.join(base_relation).sort_by { |r| [ r.id, r.name, r.age ] } }
159
+
160
+ it_should_behave_like 'a generated SQL SELECT query'
161
+
162
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age" LIMIT 1') }
163
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age" LIMIT 1)') }
164
+ end
165
+ end
@@ -0,0 +1,165 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Unary, '#visit_axiom_relation_operation_offset' do
6
+ subject { object.visit_axiom_relation_operation_offset(offset) }
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(:offset) { operand.drop(1) }
16
+ let(:object) { described_class.new }
17
+
18
+ context 'when the operand is a base relation' do
19
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
20
+
21
+ it_should_behave_like 'a generated SQL SELECT query'
22
+
23
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') }
24
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') }
25
+ end
26
+
27
+ context 'when the operand is a projection' do
28
+ let(:operand) { base_relation.project([ :id, :name ]).sort_by { |r| [ r.id, r.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" ORDER BY "id", "name" OFFSET 1') }
33
+ its(:to_subquery) { should eql('(SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id", "name" OFFSET 1)') }
34
+ end
35
+
36
+ context 'when the operand is an extension' do
37
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) }.sort_by { |r| [ r.id, r.name, r.age, r.one ] } }
38
+
39
+ it_should_behave_like 'a generated SQL SELECT query'
40
+
41
+ its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one" OFFSET 1') }
42
+ its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one" OFFSET 1)') }
43
+ end
44
+
45
+ context 'when the operand is a rename' do
46
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.rename(:id => :user_id) }
47
+
48
+ it_should_behave_like 'a generated SQL SELECT query'
49
+
50
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') }
51
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') }
52
+ end
53
+
54
+ context 'when the operand is a restriction' do
55
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) }.sort_by { |r| [ r.id, r.name, r.age ] } }
56
+
57
+ it_should_behave_like 'a generated SQL SELECT query'
58
+
59
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age" OFFSET 1') }
60
+ its(:to_subquery) { should eql('(SELECT * FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age" OFFSET 1)') }
61
+ end
62
+
63
+ context 'when the operand is a summarization' do
64
+ context 'summarize per table dee' do
65
+ let(:summarize_per) { TABLE_DEE }
66
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } }
67
+
68
+ it_should_behave_like 'a generated SQL SELECT query'
69
+
70
+ its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" OFFSET 1') }
71
+ its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" OFFSET 1)') }
72
+ end
73
+
74
+ context 'summarize per table dum' do
75
+ let(:summarize_per) { TABLE_DUM }
76
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } }
77
+
78
+ it_should_behave_like 'a generated SQL SELECT query'
79
+
80
+ its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" OFFSET 1') }
81
+ its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" OFFSET 1)') }
82
+ end
83
+
84
+ context 'summarize by a subset of the operand header' do
85
+ let(:operand) { base_relation.summarize([ :id, :name ]) { |r| r.add(:count, r.age.count) }.sort_by { |r| [ r.id, r.name, r.count ] } }
86
+
87
+ it_should_behave_like 'a generated SQL SELECT query'
88
+
89
+ its(:to_s) { should eql('SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count" OFFSET 1') }
90
+ its(:to_subquery) { should eql('(SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count" OFFSET 1)') }
91
+ end
92
+ end
93
+
94
+ context 'when the operand is ordered' do
95
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
96
+
97
+ it_should_behave_like 'a generated SQL SELECT query'
98
+
99
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') }
100
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') }
101
+ end
102
+
103
+ context 'when the operand is reversed' do
104
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
105
+
106
+ it_should_behave_like 'a generated SQL SELECT query'
107
+
108
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC OFFSET 1') }
109
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC OFFSET 1)') }
110
+ end
111
+
112
+ context 'when the operand is limited' do
113
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.take(1) }
114
+
115
+ it_should_behave_like 'a generated SQL SELECT query'
116
+
117
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" OFFSET 1') }
118
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" OFFSET 1)') }
119
+ end
120
+
121
+ context 'when the operand is an offset' do
122
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.drop(1) }
123
+
124
+ it_should_behave_like 'a generated SQL SELECT query'
125
+
126
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" OFFSET 1') }
127
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" OFFSET 1)') }
128
+ end
129
+
130
+ context 'when the operand is a difference' do
131
+ let(:operand) { base_relation.difference(base_relation).sort_by { |r| [ r.id, r.name, r.age ] } }
132
+
133
+ it_should_behave_like 'a generated SQL SELECT query'
134
+
135
+ 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" ORDER BY "id", "name", "age" OFFSET 1') }
136
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" OFFSET 1)') }
137
+ end
138
+
139
+ context 'when the operand is an intersection' do
140
+ let(:operand) { base_relation.intersect(base_relation).sort_by { |r| [ r.id, r.name, r.age ] } }
141
+
142
+ it_should_behave_like 'a generated SQL SELECT query'
143
+
144
+ 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" ORDER BY "id", "name", "age" OFFSET 1') }
145
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" OFFSET 1)') }
146
+ end
147
+
148
+ context 'when the operand is a union' do
149
+ let(:operand) { base_relation.union(base_relation).sort_by { |r| [ r.id, r.name, r.age ] } }
150
+
151
+ it_should_behave_like 'a generated SQL SELECT query'
152
+
153
+ 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" ORDER BY "id", "name", "age" OFFSET 1') }
154
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" OFFSET 1)') }
155
+ end
156
+
157
+ context 'when the operand is a join' do
158
+ let(:operand) { base_relation.join(base_relation).sort_by { |r| [ r.id, r.name, r.age ] } }
159
+
160
+ it_should_behave_like 'a generated SQL SELECT query'
161
+
162
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age" OFFSET 1') }
163
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age" OFFSET 1)') }
164
+ end
165
+ end
@@ -0,0 +1,183 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe SQL::Generator::Relation::Unary, '#visit_axiom_relation_operation_order' do
6
+ subject { object.visit_axiom_relation_operation_order(order) }
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(:order) { operand.sort_by { |r| [ r.id, r.name, r.age ] } }
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" ORDER BY "id", "name", "age"') }
24
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age")') }
25
+ end
26
+
27
+ context 'when the operand is a projection' do
28
+ let(:order) { operand.sort_by { |r| [ r.id, r.name ] } }
29
+
30
+ context 'when the projection contains the base_relation' do
31
+ let(:operand) { base_relation.project([ :id, :name ]) }
32
+
33
+ it_should_behave_like 'a generated SQL SELECT query'
34
+
35
+ its(:to_s) { should eql('SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id", "name"') }
36
+ its(:to_subquery) { should eql('(SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id", "name")') }
37
+ end
38
+
39
+ context 'when the projection contains an order' do
40
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.project([ :id, :name ]) }
41
+
42
+ it_should_behave_like 'a generated SQL SELECT query'
43
+
44
+ its(:to_s) { should eql('SELECT DISTINCT "id", "name" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" ORDER BY "id", "name"') }
45
+ its(:to_subquery) { should eql('(SELECT DISTINCT "id", "name" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age") AS "users" ORDER BY "id", "name")') }
46
+ end
47
+ end
48
+
49
+ context 'when the operand is an extension' do
50
+ let(:operand) { base_relation.extend { |r| r.add(:one, 1) }.sort_by { |r| [ r.id, r.name, r.age, r.one ] } }
51
+ let(:order) { operand.sort_by { |r| [ r.id, r.name, r.age, r.one ] } }
52
+
53
+ it_should_behave_like 'a generated SQL SELECT query'
54
+
55
+ its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one"') }
56
+ its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one")') }
57
+ end
58
+
59
+ context 'when the operand is a rename' do
60
+ let(:operand) { base_relation.rename(:id => :user_id) }
61
+ let(:order) { operand.sort_by { |r| [ r.user_id, r.name, r.age ] } }
62
+
63
+ it_should_behave_like 'a generated SQL SELECT query'
64
+
65
+ its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "user_id", "name", "age"') }
66
+ its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "user_id", "name", "age")') }
67
+ end
68
+
69
+ context 'when the operand is a restriction' do
70
+ let(:operand) { base_relation.restrict { |r| r.id.eq(1) } }
71
+
72
+ it_should_behave_like 'a generated SQL SELECT query'
73
+
74
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age"') }
75
+ its(:to_subquery) { should eql('(SELECT * FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age")') }
76
+ end
77
+
78
+ context 'when the operand is a summarization' do
79
+ let(:order) { operand.sort_by { |r| r.count } }
80
+
81
+ context 'summarize per table dee' do
82
+ let(:summarize_per) { TABLE_DEE }
83
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } }
84
+
85
+ it_should_behave_like 'a generated SQL SELECT query'
86
+
87
+ its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count"') }
88
+ its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count")') }
89
+ end
90
+
91
+ context 'summarize per table dum' do
92
+ let(:summarize_per) { TABLE_DUM }
93
+ let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } }
94
+
95
+ it_should_behave_like 'a generated SQL SELECT query'
96
+
97
+ its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count"') }
98
+ its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count")') }
99
+ end
100
+
101
+ context 'summarize by a subset of the operand header' do
102
+ let(:operand) { base_relation.summarize([ :id, :name ]) { |r| r.add(:count, r.age.count) }.sort_by { |r| [ r.id, r.name, r.count ] } }
103
+ let(:order) { operand.sort_by { |r| [ r.id, r.name, r.count ] } }
104
+
105
+ it_should_behave_like 'a generated SQL SELECT query'
106
+
107
+ its(:to_s) { should eql('SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count"') }
108
+ its(:to_subquery) { should eql('(SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count")') }
109
+ end
110
+ end
111
+
112
+ context 'when the operand is ordered' do
113
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] } }
114
+
115
+ it_should_behave_like 'a generated SQL SELECT query'
116
+
117
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age"') }
118
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age")') }
119
+ end
120
+
121
+ context 'when the operand is reversed' do
122
+ let(:operand) { base_relation.sort_by { |r| [ r.id, r.name, r.age ] }.reverse }
123
+
124
+ it_should_behave_like 'a generated SQL SELECT query'
125
+
126
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age"') }
127
+ its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age")') }
128
+ end
129
+
130
+ context 'when the operand is limited' do
131
+ let(:operand) { base_relation.sort_by{ [ id.desc, name.desc, age.desc ] }.take(1) }
132
+
133
+ it_should_behave_like 'a generated SQL SELECT query'
134
+
135
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC LIMIT 1) AS "users" ORDER BY "id", "name", "age"') }
136
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC LIMIT 1) AS "users" ORDER BY "id", "name", "age")') }
137
+ end
138
+
139
+ context 'when the operand is an offset' do
140
+ let(:operand) { base_relation.sort_by{ [ id.desc, name.desc, age.desc ] }.drop(1) }
141
+
142
+ it_should_behave_like 'a generated SQL SELECT query'
143
+
144
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC OFFSET 1) AS "users" ORDER BY "id", "name", "age"') }
145
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC OFFSET 1) AS "users" ORDER BY "id", "name", "age")') }
146
+ end
147
+
148
+ context 'when the operand is a difference' do
149
+ let(:operand) { base_relation.difference(base_relation) }
150
+
151
+ it_should_behave_like 'a generated SQL SELECT query'
152
+
153
+ 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" ORDER BY "id", "name", "age"') }
154
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age")') }
155
+ end
156
+
157
+ context 'when the operand is an intersection' do
158
+ let(:operand) { base_relation.intersect(base_relation) }
159
+
160
+ it_should_behave_like 'a generated SQL SELECT query'
161
+
162
+ 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" ORDER BY "id", "name", "age"') }
163
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age")') }
164
+ end
165
+
166
+ context 'when the operand is a union' do
167
+ let(:operand) { base_relation.union(base_relation) }
168
+
169
+ it_should_behave_like 'a generated SQL SELECT query'
170
+
171
+ 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" ORDER BY "id", "name", "age"') }
172
+ its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age")') }
173
+ end
174
+
175
+ context 'when the operand is a join' do
176
+ let(:operand) { base_relation.join(base_relation) }
177
+
178
+ it_should_behave_like 'a generated SQL SELECT query'
179
+
180
+ its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age"') }
181
+ its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age")') }
182
+ end
183
+ end