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,28 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module SQL
5
+ module Generator
6
+
7
+ # Generates an SQL statement for an identifier
8
+ module Identifier
9
+
10
+ QUOTE = '"'.freeze
11
+ ESCAPED_QUOTE = '""'.freeze
12
+
13
+ # Quote the identifier
14
+ #
15
+ # @param [#to_s] identifier
16
+ #
17
+ # @return [#to_s]
18
+ #
19
+ # @api private
20
+ def visit_identifier(identifier)
21
+ escaped = identifier.to_s.gsub(QUOTE, ESCAPED_QUOTE)
22
+ escaped.insert(0, QUOTE) << QUOTE
23
+ end
24
+
25
+ end # module Identifier
26
+ end # module Generator
27
+ end # module SQL
28
+ end # module Axiom
@@ -0,0 +1,157 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module SQL
5
+ module Generator
6
+
7
+ # Generates an SQL statement for a literal
8
+ module Literal
9
+
10
+ TRUE = 'TRUE'.freeze
11
+ FALSE = 'FALSE'.freeze
12
+ NULL = 'NULL'.freeze
13
+ QUOTE = "'".freeze
14
+ ESCAPED_QUOTE = "''".freeze
15
+ SEPARATOR = ', '.freeze
16
+ TIME_SCALE = 9
17
+
18
+ # Returns an unfrozen object
19
+ #
20
+ # Some objects, like Date, DateTime and Time memoize values
21
+ # when serialized to a String, so when they are frozen this will
22
+ # dup them and then return the unfrozen copy.
23
+ #
24
+ # @param [Object] object
25
+ #
26
+ # @return [Object]
27
+ # non-frozen object
28
+ #
29
+ # @api private
30
+ def self.dup_frozen(object)
31
+ object.frozen? ? object.dup : object
32
+ end
33
+
34
+ # Visit an Enumerable
35
+ #
36
+ # @param [Enumerable] enumerable
37
+ #
38
+ # @return [#to_s]
39
+ #
40
+ # @api private
41
+ def visit_enumerable(enumerable)
42
+ Generator.parenthesize!(
43
+ enumerable.map { |entry| dispatch entry }.join(SEPARATOR)
44
+ )
45
+ end
46
+
47
+ # Visit a String
48
+ #
49
+ # @note The string must be UTF-8 encoded
50
+ #
51
+ # @param [String] string
52
+ #
53
+ # @return [#to_s]
54
+ #
55
+ # @api private
56
+ def visit_string(string)
57
+ escaped = string.gsub(QUOTE, ESCAPED_QUOTE)
58
+ escaped.insert(0, QUOTE) << QUOTE
59
+ end
60
+
61
+ # Visit a Numeric
62
+ #
63
+ # @param [Numeric] numeric
64
+ #
65
+ # @return [#to_s]
66
+ #
67
+ # @api private
68
+ def visit_numeric(numeric)
69
+ numeric.to_s
70
+ end
71
+
72
+ # Visit a Class
73
+ #
74
+ # @param [Class] klass
75
+ #
76
+ # @return [#to_s]
77
+ #
78
+ # @api private
79
+ def visit_class(klass)
80
+ name = klass.name.to_s
81
+ name.empty? ? NULL : visit_string(name)
82
+ end
83
+
84
+ # Visit a Date and return in ISO 8601 date format
85
+ #
86
+ # @param [Date] date
87
+ #
88
+ # @return [#to_s]
89
+ #
90
+ # @api private
91
+ def visit_date(date)
92
+ dispatch date.iso8601
93
+ end
94
+
95
+ # Visit a DateTime and return in ISO 8601 date-time format
96
+ #
97
+ # Converts the DateTime to UTC format.
98
+ #
99
+ # @param [DateTime] date_time
100
+ #
101
+ # @return [#to_s]
102
+ #
103
+ # @api private
104
+ def visit_date_time(date_time)
105
+ dispatch date_time.new_offset.iso8601(TIME_SCALE)
106
+ end
107
+
108
+ # Visit a Time
109
+ #
110
+ # Converts the Time to UTC format.
111
+ #
112
+ # @param [Time] time
113
+ #
114
+ # @return [#to_s]
115
+ #
116
+ # @api private
117
+ def visit_time(time)
118
+ dispatch Literal.dup_frozen(time).utc.iso8601(TIME_SCALE)
119
+ end
120
+
121
+ # Visit a true value
122
+ #
123
+ # @param [true] _true
124
+ #
125
+ # @return [#to_s]
126
+ #
127
+ # @api private
128
+ def visit_true_class(_true)
129
+ TRUE
130
+ end
131
+
132
+ # Visit a false value
133
+ #
134
+ # @param [false] _false
135
+ #
136
+ # @return [#to_s]
137
+ #
138
+ # @api private
139
+ def visit_false_class(_false)
140
+ FALSE
141
+ end
142
+
143
+ # Visit a nil value
144
+ #
145
+ # @param [nil] _nil
146
+ #
147
+ # @return [#to_s]
148
+ #
149
+ # @api private
150
+ def visit_nil_class(_nil)
151
+ NULL
152
+ end
153
+
154
+ end # module Literal
155
+ end # module Generator
156
+ end # module SQL
157
+ end # module Axiom
@@ -0,0 +1,240 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module SQL
5
+ module Generator
6
+
7
+ # Abstract base class for SQL generation from a relation
8
+ class Relation < Visitor
9
+ extend Identifier
10
+ include Attribute
11
+
12
+ EMPTY_STRING = ''.freeze
13
+ SEPARATOR = ', '.freeze
14
+ STAR = '*'.freeze
15
+ EMPTY_HASH = {}.freeze
16
+
17
+ # Return the alias name
18
+ #
19
+ # @return [#to_s]
20
+ #
21
+ # @api private
22
+ attr_reader :name
23
+
24
+ # Factory method to instantiate the generator for the relation
25
+ #
26
+ # @param [Axiom::Relation] relation
27
+ #
28
+ # @return [Generator::Relation]
29
+ #
30
+ # @api private
31
+ def self.visit(relation)
32
+ klass = case relation
33
+ when Axiom::Relation::Operation::Insertion then self::Insertion
34
+ when Axiom::Relation::Operation::Set then self::Set
35
+ when Axiom::Relation::Operation::Binary then self::Binary
36
+ when Axiom::Relation::Operation::Unary then self::Unary
37
+ when Axiom::Relation::Base then self::Base
38
+ when Axiom::Relation::Materialized then self::Materialized
39
+ else
40
+ raise InvalidRelationError, "#{relation.class} is not a visitable relation"
41
+ end
42
+ klass.new.visit(relation)
43
+ end
44
+
45
+ # Initialize a Generator
46
+ #
47
+ # @return [undefined]
48
+ #
49
+ # @api private
50
+ def initialize
51
+ @sql = EMPTY_STRING
52
+ @extensions = {}
53
+ end
54
+
55
+ # Visit an object and generate SQL from each node
56
+ #
57
+ # @example
58
+ # generator.visit(visitable)
59
+ #
60
+ # @param [Visitable] visitable
61
+ # A visitable object
62
+ #
63
+ # @return [self]
64
+ #
65
+ # @raise [Visitor::UnknownObject]
66
+ # raised when the visitable object has no handler
67
+ #
68
+ # @api public
69
+ def visit(visitable)
70
+ @sql = dispatch(visitable).to_s.freeze
71
+ freeze
72
+ end
73
+
74
+ # Returns the current SQL string
75
+ #
76
+ # @example
77
+ # sql = generator.to_sql
78
+ #
79
+ # @return [String]
80
+ #
81
+ # @api public
82
+ def to_sql
83
+ @sql
84
+ end
85
+
86
+ # Return the SQL for the unary relation
87
+ #
88
+ # @example
89
+ # sql = unary_relation.to_s
90
+ #
91
+ # @return [#to_s]
92
+ #
93
+ # @api public
94
+ def to_s
95
+ return EMPTY_STRING unless visited?
96
+ generate_sql(query_columns)
97
+ end
98
+
99
+ # Return the SQL suitable for an subquery
100
+ #
101
+ # @return [#to_s]
102
+ #
103
+ # @api private
104
+ def to_subquery
105
+ return EMPTY_STRING unless visited?
106
+ Generator.parenthesize!(generate_sql(subquery_columns))
107
+ end
108
+
109
+ # Test if a visitable object has been visited
110
+ #
111
+ # @example
112
+ # visitor.visited? # true or false
113
+ #
114
+ # @return [Boolean]
115
+ #
116
+ # @api public
117
+ def visited?
118
+ instance_variable_defined?(:@name)
119
+ end
120
+
121
+ private
122
+
123
+ # Return the columns to use in a query
124
+ #
125
+ # @return [#to_s]
126
+ #
127
+ # @api private
128
+ def query_columns
129
+ explicit_columns
130
+ end
131
+
132
+ # Return the columns to use in a subquery
133
+ #
134
+ # @return [#to_s]
135
+ #
136
+ # @api private
137
+ def subquery_columns
138
+ implicit_columns
139
+ end
140
+
141
+ # Return the implicit columns for the select list
142
+ #
143
+ # @return [#to_s]
144
+ #
145
+ # @api private
146
+ def implicit_columns
147
+ sql = [ STAR, column_list_for(@extensions) ]
148
+ sql.reject! { |fragment| fragment.empty? }
149
+ sql.join(SEPARATOR)
150
+ end
151
+
152
+ # Return the explicit columns for the select list
153
+ #
154
+ # @return [#to_s]
155
+ #
156
+ # @api private
157
+ def explicit_columns
158
+ @distinct.to_s + column_list_for(@extensions.merge(@columns || EMPTY_HASH))
159
+ end
160
+
161
+ # Return the list of columns
162
+ #
163
+ # @param [#map] columns
164
+ #
165
+ # @return [#to_s]
166
+ #
167
+ # @api private
168
+ def column_list_for(columns)
169
+ sql = columns.values_at(*@header)
170
+ sql.compact!
171
+ sql.join(SEPARATOR)
172
+ end
173
+
174
+ # Return a list of columns in a header
175
+ #
176
+ # @param [Axiom::Relation] relation
177
+ #
178
+ # @param [#[]] aliases
179
+ # optional aliases for the columns
180
+ #
181
+ # @return [Hash]
182
+ #
183
+ # @api private
184
+ def columns_for(relation, aliases = EMPTY_HASH)
185
+ columns = {}
186
+ relation.header.each do |attribute|
187
+ columns[aliases.fetch(attribute, attribute)] = column_for(attribute, aliases)
188
+ end
189
+ columns
190
+ end
191
+
192
+ # Return the column for an attribute
193
+ #
194
+ # @param [Attribute] attribute
195
+ #
196
+ # @param [#[]] aliases
197
+ # aliases for the columns
198
+ #
199
+ # @return [#to_s]
200
+ #
201
+ # @api private
202
+ def column_for(attribute, aliases)
203
+ if aliases.key?(attribute)
204
+ alias_for(attribute, aliases[attribute])
205
+ else
206
+ dispatch(attribute)
207
+ end
208
+ end
209
+
210
+ # Return the column alias for an attribute
211
+ #
212
+ # @param [#to_s] attribute
213
+ #
214
+ # @param [Attribute, nil] alias_attribute
215
+ # attribute to use for the alias
216
+ #
217
+ # @return [#to_s]
218
+ #
219
+ # @api private
220
+ def alias_for(attribute, alias_attribute)
221
+ "#{dispatch(attribute)} AS #{dispatch(alias_attribute)}"
222
+ end
223
+
224
+ # Add extensions for extension and summarize queries
225
+ #
226
+ # @param [#each] extensions
227
+ #
228
+ # @return [undefined]
229
+ #
230
+ # @api private
231
+ def add_extensions(extensions)
232
+ extensions.each do |attribute, function|
233
+ @extensions[attribute] = alias_for(function, attribute)
234
+ end
235
+ end
236
+
237
+ end # class Relation
238
+ end # module Generator
239
+ end # module SQL
240
+ end # module Axiom
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module SQL
5
+ module Generator
6
+ class Relation
7
+
8
+ # Generates an SQL statement for a base relation
9
+ class Base < Unary; end
10
+
11
+ end # class Relation
12
+ end # module Generator
13
+ end # module SQL
14
+ end # module Axiom
@@ -0,0 +1,136 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module SQL
5
+ module Generator
6
+ class Relation
7
+
8
+ # Generates an SQL statement for a Binary relation
9
+ class Binary < Relation
10
+
11
+ JOIN = 'NATURAL JOIN'.freeze
12
+ PRODUCT = 'CROSS JOIN'.freeze
13
+ LEFT_NAME = 'left'.freeze
14
+ RIGHT_NAME = 'right'.freeze
15
+
16
+ # Visit an Join
17
+ #
18
+ # @param [Algebra::Join] join
19
+ #
20
+ # @return [self]
21
+ #
22
+ # @api private
23
+ def visit_axiom_algebra_join(join)
24
+ @header = join.header
25
+ set_operation(JOIN)
26
+ set_columns(join)
27
+ set_operands(join)
28
+ set_name
29
+ self
30
+ end
31
+
32
+ # Visit an Product
33
+ #
34
+ # @param [Algebra::Product] product
35
+ #
36
+ # @return [self]
37
+ #
38
+ # @api private
39
+ def visit_axiom_algebra_product(product)
40
+ @header = product.header
41
+ set_operation(PRODUCT)
42
+ set_columns(product)
43
+ set_operands(product)
44
+ set_name
45
+ self
46
+ end
47
+
48
+ private
49
+
50
+ # Generate the SQL using the supplied columns
51
+ #
52
+ # @param [String] columns
53
+ #
54
+ # @return [#to_s]
55
+ #
56
+ # @api private
57
+ def generate_sql(columns)
58
+ "SELECT #{columns} FROM #{@left.to_subquery} AS #{visit_identifier(LEFT_NAME)} #{@operation} #{@right.to_subquery} AS #{visit_identifier(RIGHT_NAME)}"
59
+ end
60
+
61
+ # Set the operation
62
+ #
63
+ # @param [#to_s] operation
64
+ #
65
+ # @return [undefined]
66
+ #
67
+ # @api private
68
+ def set_operation(operation)
69
+ @operation = operation
70
+ end
71
+
72
+ # Set the columns from the relation
73
+ #
74
+ # @param [Relation::Operation::Binary] relation
75
+ #
76
+ # @return [undefined]
77
+ #
78
+ # @api private
79
+ def set_columns(relation)
80
+ @columns = columns_for(relation)
81
+ end
82
+
83
+ # Set the operands from the relation
84
+ #
85
+ # @param [Relation::Operation::Binary] relation
86
+ #
87
+ # @return [undefined]
88
+ #
89
+ # @api private
90
+ def set_operands(relation)
91
+ util = self.class
92
+ @left = util.visit(relation.left)
93
+ @right = util.visit(relation.right)
94
+ end
95
+
96
+ # Set the name using the operands' name
97
+ #
98
+ # @return [undefined]
99
+ #
100
+ # @api private
101
+ def set_name
102
+ @name = [ @left.name, @right.name ].uniq.join(UNDERSCORE).freeze
103
+ end
104
+
105
+ # Generates an SQL statement for base relation binary operands
106
+ class Base < Relation::Base
107
+
108
+ # Return the SQL suitable for an subquery
109
+ #
110
+ # Does not parenthesize the query
111
+ #
112
+ # @return [#to_s]
113
+ #
114
+ # @api private
115
+ def to_subquery
116
+ return EMPTY_STRING unless visited?
117
+ generate_sql
118
+ end
119
+
120
+ private
121
+
122
+ # Generate the SQL for this base relation
123
+ #
124
+ # @return [#to_s]
125
+ #
126
+ # @api private
127
+ def generate_sql(*)
128
+ @from
129
+ end
130
+
131
+ end # class Base
132
+ end # class Binary
133
+ end # class Relation
134
+ end # module Generator
135
+ end # module SQL
136
+ end # module Axiom