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,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