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,62 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module SQL
5
+ module Generator
6
+ class Relation
7
+
8
+ # Generates an SQL statement for an insertion
9
+ class Insertion < Set
10
+ extend Aliasable
11
+
12
+ inheritable_alias(:to_subquery => :to_s)
13
+
14
+ # Visit an Insertion
15
+ #
16
+ # @param [Relation::Operation::Insertion] insertion
17
+ #
18
+ # @return [self]
19
+ #
20
+ # @api private
21
+ def visit_axiom_relation_operation_insertion(insertion)
22
+ @header = insertion.header
23
+ set_columns(insertion)
24
+ set_operands(insertion)
25
+ set_name
26
+ self
27
+ end
28
+
29
+ private
30
+
31
+ # Generate the SQL using the supplied method
32
+ #
33
+ # @return [#to_s]
34
+ #
35
+ # @api private
36
+ def generate_sql(*)
37
+ "INSERT INTO #{@name} #{column_list} #{@right}"
38
+ end
39
+
40
+ # Generate the list of columns to insert into
41
+ #
42
+ # @return [#to_s]
43
+ #
44
+ # @api private
45
+ def column_list
46
+ Generator.parenthesize!(column_list_for(@columns))
47
+ end
48
+
49
+ # Set the name using the left operands' name
50
+ #
51
+ # @return [undefined]
52
+ #
53
+ # @api private
54
+ def set_name
55
+ @name = @left.name
56
+ end
57
+
58
+ end # class Insertion
59
+ end # class Relation
60
+ end # module Generator
61
+ end # module SQL
62
+ end # module Axiom
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module SQL
5
+ module Generator
6
+ class Relation
7
+
8
+ # Generates an SQL statement for materialized relation
9
+ class Materialized < Relation
10
+ include Literal
11
+
12
+ # Visit a Materialized relation
13
+ #
14
+ # @param [Relation::Materialized] materialized
15
+ #
16
+ # @return [self]
17
+ #
18
+ # @api private
19
+ def visit_axiom_relation_materialized(materialized)
20
+ @values = materialized.map do |tuple|
21
+ Generator.parenthesize!(
22
+ tuple.to_ary.map { |value| dispatch(value) }.join(', ')
23
+ )
24
+ end
25
+ self
26
+ end
27
+
28
+ # Test if a visitable object has been visited
29
+ #
30
+ # @example
31
+ # visitor.visited? # true or false
32
+ #
33
+ # @return [Boolean]
34
+ #
35
+ # @api public
36
+ def visited?
37
+ instance_variable_defined?(:@values)
38
+ end
39
+
40
+ private
41
+
42
+ # Generate the SQL for the materialized relation
43
+ #
44
+ # @return [#to_s]
45
+ #
46
+ # @api private
47
+ def generate_sql(*)
48
+ return EMPTY_STRING unless visited?
49
+ if @values.empty?
50
+ 'SELECT 0 LIMIT 0' # no values
51
+ else
52
+ "VALUES #{@values.join(', ')}"
53
+ end
54
+ end
55
+
56
+ end # class Materialized
57
+ end # class Relation
58
+ end # module Generator
59
+ end # module SQL
60
+ end # module Axiom
@@ -0,0 +1,107 @@
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 set relation
9
+ class Set < Binary
10
+
11
+ DIFFERENCE = 'EXCEPT'.freeze
12
+ INTERSECTION = 'INTERSECT'.freeze
13
+ UNION = 'UNION'.freeze
14
+
15
+ # Normalize the headers of the operands
16
+ #
17
+ # This is necessary to make sure the columns are in the correct
18
+ # order when generating SQL.
19
+ #
20
+ # @param [Relation::Operation::Set] relation
21
+ #
22
+ # @return [Relation::Operation::Set]
23
+ #
24
+ # @api private
25
+ def self.normalize_operand_headers(relation)
26
+ left = relation.left
27
+ right = relation.right
28
+ left_header = left.header
29
+ if left_header.to_a != right.header.to_a
30
+ relation.class.new(left, right.project(left_header))
31
+ else
32
+ relation
33
+ end
34
+ end
35
+
36
+ # Visit a Union
37
+ #
38
+ # @param [Algebra::Union] union
39
+ #
40
+ # @return [self]
41
+ #
42
+ # @api private
43
+ def visit_axiom_algebra_union(union)
44
+ set_operation(UNION)
45
+ set_operands(union)
46
+ set_name
47
+ self
48
+ end
49
+
50
+ # Visit an Intersection
51
+ #
52
+ # @param [Algebra::Intersection] intersection
53
+ #
54
+ # @return [self]
55
+ #
56
+ # @api private
57
+ def visit_axiom_algebra_intersection(intersection)
58
+ set_operation(INTERSECTION)
59
+ set_operands(intersection)
60
+ set_name
61
+ self
62
+ end
63
+
64
+ # Visit an Difference
65
+ #
66
+ # @param [Algebra::Difference] difference
67
+ #
68
+ # @return [self]
69
+ #
70
+ # @api private
71
+ def visit_axiom_algebra_difference(difference)
72
+ set_operation(DIFFERENCE)
73
+ set_operands(difference)
74
+ set_name
75
+ self
76
+ end
77
+
78
+ private
79
+
80
+ # Generate the SQL using the supplied method
81
+ #
82
+ # @return [#to_s]
83
+ #
84
+ # @api private
85
+ def generate_sql(*)
86
+ "(#{@left}) #{@operation} (#{@right})"
87
+ end
88
+
89
+ # Set the operands from the relation
90
+ #
91
+ # @param [Relation::Operation::Set] relation
92
+ #
93
+ # @return [undefined]
94
+ #
95
+ # @api private
96
+ def set_operands(relation)
97
+ super self.class.normalize_operand_headers(relation)
98
+ end
99
+
100
+ # Generates an SQL statement for base relation set operands
101
+ class Base < Relation::Base; end
102
+
103
+ end # class Set
104
+ end # class Relation
105
+ end # module Generator
106
+ end # module SQL
107
+ end # module Axiom
@@ -0,0 +1,379 @@
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 unary relation
9
+ class Unary < Relation
10
+ extend Aliasable
11
+ include Direction,
12
+ Literal,
13
+ Function::Aggregate,
14
+ Function::Connective,
15
+ Function::Predicate,
16
+ Function::Proposition,
17
+ Function::String,
18
+ Function::Numeric
19
+
20
+ inheritable_alias(:visit_axiom_relation_operation_reverse => :visit_axiom_relation_operation_order)
21
+
22
+ DISTINCT = 'DISTINCT '.freeze
23
+ NO_ROWS = ' HAVING FALSE'.freeze
24
+ ANY_ROWS = ' HAVING COUNT (*) > 0'
25
+ COLLAPSIBLE = {
26
+ Algebra::Summarization => Set[ ].freeze,
27
+ Algebra::Projection => Set[ Algebra::Projection, Algebra::Restriction, ].freeze,
28
+ Algebra::Extension => Set[ Algebra::Projection, Algebra::Restriction, Axiom::Relation::Operation::Order, Axiom::Relation::Operation::Reverse, Axiom::Relation::Operation::Offset, Axiom::Relation::Operation::Limit ].freeze,
29
+ Algebra::Rename => Set[ Algebra::Projection, Algebra::Restriction, Axiom::Relation::Operation::Order, Axiom::Relation::Operation::Reverse, Axiom::Relation::Operation::Offset, Axiom::Relation::Operation::Limit ].freeze,
30
+ Algebra::Restriction => Set[ Algebra::Projection, Axiom::Relation::Operation::Order, Axiom::Relation::Operation::Reverse, ].freeze,
31
+ Axiom::Relation::Operation::Order => Set[ Algebra::Projection, Algebra::Extension, Algebra::Rename, Algebra::Restriction, Algebra::Summarization, Axiom::Relation::Operation::Order, Axiom::Relation::Operation::Reverse, ].freeze,
32
+ Axiom::Relation::Operation::Reverse => Set[ Algebra::Projection, Algebra::Extension, Algebra::Rename, Algebra::Restriction, Algebra::Summarization, Axiom::Relation::Operation::Order, Axiom::Relation::Operation::Reverse, ].freeze,
33
+ Axiom::Relation::Operation::Offset => Set[ Algebra::Projection, Algebra::Extension, Algebra::Rename, Algebra::Restriction, Algebra::Summarization, Axiom::Relation::Operation::Order, Axiom::Relation::Operation::Reverse, ].freeze,
34
+ Axiom::Relation::Operation::Limit => Set[ Algebra::Projection, Algebra::Extension, Algebra::Rename, Algebra::Restriction, Algebra::Summarization, Axiom::Relation::Operation::Order, Axiom::Relation::Operation::Reverse, Axiom::Relation::Operation::Offset, ].freeze,
35
+ }.freeze
36
+
37
+ # Initialize a Unary relation SQL generator
38
+ #
39
+ # @return [undefined]
40
+ #
41
+ # @api private
42
+ def initialize
43
+ super
44
+ @scope = ::Set.new
45
+ end
46
+
47
+ # Visit a Base Relation
48
+ #
49
+ # @param [Relation::Base] base_relation
50
+ #
51
+ # @return [self]
52
+ #
53
+ # @api private
54
+ def visit_axiom_relation_base(base_relation)
55
+ @name = base_relation.name
56
+ @from = visit_identifier(@name)
57
+ @header = base_relation.header
58
+ @columns = columns_for(base_relation)
59
+ self
60
+ end
61
+
62
+ # Visit a Projection
63
+ #
64
+ # @param [Algebra::Projection] projection
65
+ #
66
+ # @return [self]
67
+ #
68
+ # @api private
69
+ def visit_axiom_algebra_projection(projection)
70
+ @from = subquery_for(projection)
71
+ @distinct = DISTINCT
72
+ @header = projection.header
73
+ @columns = columns_for(projection)
74
+ scope_query(projection)
75
+ self
76
+ end
77
+
78
+ # Visit an Extension
79
+ #
80
+ # @param [Algebra::Extension] extension
81
+ #
82
+ # @return [self]
83
+ #
84
+ # @api private
85
+ def visit_axiom_algebra_extension(extension)
86
+ @from = subquery_for(extension)
87
+ @header = extension.header
88
+ @columns ||= columns_for(extension.operand)
89
+ add_extensions(extension.extensions)
90
+ scope_query(extension)
91
+ self
92
+ end
93
+
94
+ # Visit a Rename
95
+ #
96
+ # @param [Algebra::Rename] rename
97
+ #
98
+ # @return [self]
99
+ #
100
+ # @api private
101
+ def visit_axiom_algebra_rename(rename)
102
+ @from = subquery_for(rename)
103
+ @header = rename.header
104
+ @columns = columns_for(rename.operand, rename.aliases.to_hash)
105
+ scope_query(rename)
106
+ self
107
+ end
108
+
109
+ # Visit a Restriction
110
+ #
111
+ # @param [Algebra::Restriction] restriction
112
+ #
113
+ # @return [self]
114
+ #
115
+ # @api private
116
+ def visit_axiom_algebra_restriction(restriction)
117
+ @from = subquery_for(restriction)
118
+ @where = " WHERE #{dispatch(restriction.predicate)}"
119
+ @header = restriction.header
120
+ @columns ||= columns_for(restriction)
121
+ scope_query(restriction)
122
+ self
123
+ end
124
+
125
+ # Visit a Summarization
126
+ #
127
+ # @param [Algebra::Summarization] summarization
128
+ #
129
+ # @return [self]
130
+ #
131
+ # @api private
132
+ def visit_axiom_algebra_summarization(summarization)
133
+ summarize_per = summarization.summarize_per
134
+ @from = subquery_for(summarization)
135
+ @header = summarization.header
136
+ @columns = columns_for(summarize_per)
137
+ summarize_per(summarize_per)
138
+ group_by_columns
139
+ add_extensions(summarization.summarizers)
140
+ scope_query(summarization)
141
+ self
142
+ end
143
+
144
+ # Visit an Order
145
+ #
146
+ # @param [Relation::Operation::Order] order
147
+ #
148
+ # @return [self]
149
+ #
150
+ # @api private
151
+ def visit_axiom_relation_operation_order(order)
152
+ @from = subquery_for(order)
153
+ @order = " ORDER BY #{order_for(order.directions)}"
154
+ @header = order.header
155
+ @columns ||= columns_for(order)
156
+ scope_query(order)
157
+ self
158
+ end
159
+
160
+ # Visit a Limit
161
+ #
162
+ # @param [Relation::Operation::Limit] limit
163
+ #
164
+ # @return [self]
165
+ #
166
+ # @api private
167
+ def visit_axiom_relation_operation_limit(limit)
168
+ @from = subquery_for(limit)
169
+ @limit = " LIMIT #{limit.limit}"
170
+ @header = limit.header
171
+ @columns ||= columns_for(limit)
172
+ scope_query(limit)
173
+ self
174
+ end
175
+
176
+ # Visit an Offset
177
+ #
178
+ # @param [Relation::Operation::Offset] offset
179
+ #
180
+ # @return [self]
181
+ #
182
+ # @api private
183
+ def visit_axiom_relation_operation_offset(offset)
184
+ @from = subquery_for(offset)
185
+ @offset = " OFFSET #{offset.offset}"
186
+ @header = offset.header
187
+ @columns ||= columns_for(offset)
188
+ scope_query(offset)
189
+ self
190
+ end
191
+
192
+ private
193
+
194
+ # Generate the SQL using the supplied columns
195
+ #
196
+ # @param [String] columns
197
+ #
198
+ # @return [#to_s]
199
+ #
200
+ # @api private
201
+ def generate_sql(columns)
202
+ [ "SELECT #{columns} FROM #{@from}", @where, @group, @having, @order, @limit, @offset ].join
203
+ end
204
+
205
+ # Return the columns to use in a query
206
+ #
207
+ # @return [#to_s]
208
+ #
209
+ # @api private
210
+ def query_columns
211
+ explicit_columns
212
+ end
213
+
214
+ # Return the columns to use in a subquery
215
+ #
216
+ # @return [#to_s]
217
+ #
218
+ # @api private
219
+ def subquery_columns
220
+ explicit_columns_in_subquery? ? explicit_columns : super
221
+ end
222
+
223
+ # Test if the subquery should use "*" and not specify columns explicitly
224
+ #
225
+ # @return [Boolean]
226
+ #
227
+ # @api private
228
+ def explicit_columns_in_subquery?
229
+ @scope.include?(Algebra::Projection) ||
230
+ @scope.include?(Algebra::Rename) ||
231
+ @scope.include?(Algebra::Summarization)
232
+ end
233
+
234
+ # Return a list of columns for ordering
235
+ #
236
+ # @param [DirectionSet] directions
237
+ #
238
+ # @return [#to_s]
239
+ #
240
+ # @api private
241
+ def order_for(directions)
242
+ directions.map { |direction| dispatch(direction) }.join(SEPARATOR)
243
+ end
244
+
245
+ # Summarize the operand over the provided relation
246
+ #
247
+ # @param [Relation] relation
248
+ #
249
+ # @return [undefined]
250
+ #
251
+ # @api private
252
+ def summarize_per(relation)
253
+ return if relation.eql?(TABLE_DEE)
254
+
255
+ if relation.eql?(TABLE_DUM) then summarize_per_table_dum
256
+ elsif (generator = Binary.visit(relation)).name.eql?(name) then summarize_per_subset
257
+ else
258
+ summarize_per_relation(generator)
259
+ end
260
+ end
261
+
262
+ # Summarize the operand using table dee
263
+ #
264
+ # @return [undefined]
265
+ #
266
+ # @api private
267
+ def summarize_per_table_dum
268
+ @having = NO_ROWS
269
+ end
270
+
271
+ # Summarize the operand using a subset
272
+ #
273
+ # @return [undefined]
274
+ #
275
+ # @api private
276
+ def summarize_per_subset
277
+ @having = ANY_ROWS
278
+ end
279
+
280
+ # Summarize the operand using another relation
281
+ #
282
+ # @return [undefined]
283
+ #
284
+ # @api private
285
+ def summarize_per_relation(generator)
286
+ @from = "#{generator.to_subquery} AS #{visit_identifier(generator.name)} NATURAL LEFT JOIN #{@from}"
287
+ end
288
+
289
+ # Group by the columns
290
+ #
291
+ # @return [undefined]
292
+ #
293
+ # @api private
294
+ def group_by_columns
295
+ @group = " GROUP BY #{column_list_for(@columns)}" if @columns.any?
296
+ end
297
+
298
+ # Return an expression that can be used for the FROM
299
+ #
300
+ # @param [Relation] relation
301
+ #
302
+ # @return [#to_s]
303
+ #
304
+ # @api private
305
+ def subquery_for(relation)
306
+ operand = relation.operand
307
+ subquery = dispatch(operand)
308
+ if collapse_subquery_for?(relation)
309
+ @from
310
+ else
311
+ aliased_subquery(subquery)
312
+ end
313
+ end
314
+
315
+ # Add the operand to the current scope
316
+ #
317
+ # @param [Relation] operand
318
+ #
319
+ # @return [undefined]
320
+ #
321
+ # @api private
322
+ def scope_query(operand)
323
+ @scope << operand.class
324
+ end
325
+
326
+ # Test if the relation should be collapsed
327
+ #
328
+ # @param [Relation] relation
329
+ #
330
+ # @return [#to_s]
331
+ #
332
+ # @api private
333
+ def collapse_subquery_for?(relation)
334
+ @scope.subset?(COLLAPSIBLE.fetch(relation.class))
335
+ end
336
+
337
+ # Returns an aliased subquery
338
+ #
339
+ # @param [#to_s] subquery
340
+ #
341
+ # @return [#to_s]
342
+ #
343
+ # @api private
344
+ def aliased_subquery(subquery)
345
+ "#{subquery.to_subquery} AS #{visit_identifier(subquery.name)}"
346
+ ensure
347
+ reset_query_state
348
+ end
349
+
350
+ # Visit a Binary Relation
351
+ #
352
+ # @param [Relation::Operation::Binary] binary
353
+ #
354
+ # @return [Relation::Binary]
355
+ #
356
+ # @api private
357
+ def visit_axiom_relation_operation_binary(binary)
358
+ generator = self.class.visit(binary)
359
+ @name = generator.name
360
+ @from = aliased_subquery(generator)
361
+ generator
362
+ end
363
+
364
+ # Reset the query state
365
+ #
366
+ # @return [undefined]
367
+ #
368
+ # @api private
369
+ def reset_query_state
370
+ @scope.clear
371
+ @extensions.clear
372
+ @distinct = @columns = @where = @order = @limit = @offset = @group = @having = nil
373
+ end
374
+
375
+ end # class Unary
376
+ end # class Relation
377
+ end # module Generator
378
+ end # module SQL
379
+ end # module Axiom