veritas-sql-generator 0.0.3

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 (103) hide show
  1. data/Gemfile +33 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +27 -0
  4. data/Rakefile +25 -0
  5. data/TODO +17 -0
  6. data/config/flay.yml +3 -0
  7. data/config/flog.yml +2 -0
  8. data/config/roodi.yml +16 -0
  9. data/config/site.reek +124 -0
  10. data/config/yardstick.yml +2 -0
  11. data/lib/veritas-sql-generator.rb +3 -0
  12. data/lib/veritas/base_relation.rb +36 -0
  13. data/lib/veritas/sql/generator.rb +35 -0
  14. data/lib/veritas/sql/generator/attribute.rb +25 -0
  15. data/lib/veritas/sql/generator/direction.rb +36 -0
  16. data/lib/veritas/sql/generator/identifier.rb +27 -0
  17. data/lib/veritas/sql/generator/literal.rb +160 -0
  18. data/lib/veritas/sql/generator/logic.rb +349 -0
  19. data/lib/veritas/sql/generator/relation.rb +111 -0
  20. data/lib/veritas/sql/generator/relation/base.rb +14 -0
  21. data/lib/veritas/sql/generator/relation/binary.rb +184 -0
  22. data/lib/veritas/sql/generator/relation/set.rb +99 -0
  23. data/lib/veritas/sql/generator/relation/unary.rb +326 -0
  24. data/lib/veritas/sql/generator/version.rb +9 -0
  25. data/lib/veritas/sql/generator/visitor.rb +121 -0
  26. data/spec/rcov.opts +6 -0
  27. data/spec/shared/command_method_behavior.rb +7 -0
  28. data/spec/shared/generated_sql_behavior.rb +15 -0
  29. data/spec/shared/idempotent_method_behavior.rb +7 -0
  30. data/spec/spec.opts +3 -0
  31. data/spec/spec_helper.rb +15 -0
  32. data/spec/unit/veritas/base_relation/name_spec.rb +45 -0
  33. data/spec/unit/veritas/sql/generator/attribute/visit_veritas_attribute_spec.rb +15 -0
  34. data/spec/unit/veritas/sql/generator/direction/visit_veritas_relation_operation_order_ascending_spec.rb +15 -0
  35. data/spec/unit/veritas/sql/generator/direction/visit_veritas_relation_operation_order_descending_spec.rb +15 -0
  36. data/spec/unit/veritas/sql/generator/identifier/visit_identifier_spec.rb +26 -0
  37. data/spec/unit/veritas/sql/generator/literal/class_methods/dup_frozen_spec.rb +23 -0
  38. data/spec/unit/veritas/sql/generator/literal/visit_class_spec.rb +31 -0
  39. data/spec/unit/veritas/sql/generator/literal/visit_date_spec.rb +15 -0
  40. data/spec/unit/veritas/sql/generator/literal/visit_date_time_spec.rb +61 -0
  41. data/spec/unit/veritas/sql/generator/literal/visit_enumerable_spec.rb +15 -0
  42. data/spec/unit/veritas/sql/generator/literal/visit_false_class_spec.rb +14 -0
  43. data/spec/unit/veritas/sql/generator/literal/visit_nil_class_spec.rb +14 -0
  44. data/spec/unit/veritas/sql/generator/literal/visit_numeric_spec.rb +34 -0
  45. data/spec/unit/veritas/sql/generator/literal/visit_string_spec.rb +26 -0
  46. data/spec/unit/veritas/sql/generator/literal/visit_time_spec.rb +97 -0
  47. data/spec/unit/veritas/sql/generator/literal/visit_true_class_spec.rb +14 -0
  48. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_connective_conjunction_spec.rb +16 -0
  49. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_connective_disjunction_spec.rb +16 -0
  50. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_connective_negation_spec.rb +16 -0
  51. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_equality_spec.rb +27 -0
  52. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_exclusion_spec.rb +43 -0
  53. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_greater_than_or_equal_to_spec.rb +15 -0
  54. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_greater_than_spec.rb +15 -0
  55. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_inclusion_spec.rb +43 -0
  56. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_inequality_spec.rb +55 -0
  57. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_less_than_or_equal_to_spec.rb +15 -0
  58. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_less_than_spec.rb +15 -0
  59. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_proposition_contradiction_spec.rb +15 -0
  60. data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_proposition_tautology_spec.rb +15 -0
  61. data/spec/unit/veritas/sql/generator/relation/binary/base/to_subquery_spec.rb +35 -0
  62. data/spec/unit/veritas/sql/generator/relation/binary/base/visit_veritas_base_relation_spec.rb +22 -0
  63. data/spec/unit/veritas/sql/generator/relation/binary/class_methods/subquery_spec.rb +42 -0
  64. data/spec/unit/veritas/sql/generator/relation/binary/to_s_spec.rb +35 -0
  65. data/spec/unit/veritas/sql/generator/relation/binary/to_subquery_spec.rb +35 -0
  66. data/spec/unit/veritas/sql/generator/relation/binary/visit_veritas_algebra_join_spec.rb +138 -0
  67. data/spec/unit/veritas/sql/generator/relation/binary/visit_veritas_algebra_product_spec.rb +139 -0
  68. data/spec/unit/veritas/sql/generator/relation/class_methods/subquery_spec.rb +33 -0
  69. data/spec/unit/veritas/sql/generator/relation/class_methods/visit_spec.rb +61 -0
  70. data/spec/unit/veritas/sql/generator/relation/name_spec.rb +30 -0
  71. data/spec/unit/veritas/sql/generator/relation/set/to_s_spec.rb +55 -0
  72. data/spec/unit/veritas/sql/generator/relation/set/to_subquery_spec.rb +55 -0
  73. data/spec/unit/veritas/sql/generator/relation/set/visit_veritas_algebra_difference_spec.rb +138 -0
  74. data/spec/unit/veritas/sql/generator/relation/set/visit_veritas_algebra_intersection_spec.rb +138 -0
  75. data/spec/unit/veritas/sql/generator/relation/set/visit_veritas_algebra_union_spec.rb +138 -0
  76. data/spec/unit/veritas/sql/generator/relation/to_sql_spec.rb +52 -0
  77. data/spec/unit/veritas/sql/generator/relation/unary/to_s_spec.rb +55 -0
  78. data/spec/unit/veritas/sql/generator/relation/unary/to_subquery_spec.rb +75 -0
  79. data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_algebra_projection_spec.rb +138 -0
  80. data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_algebra_rename_spec.rb +136 -0
  81. data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_algebra_restriction_spec.rb +157 -0
  82. data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_base_relation_spec.rb +21 -0
  83. data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_relation_operation_limit_spec.rb +125 -0
  84. data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_relation_operation_offset_spec.rb +125 -0
  85. data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_relation_operation_order_spec.rb +136 -0
  86. data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_relation_operation_reverse_spec.rb +125 -0
  87. data/spec/unit/veritas/sql/generator/relation/visit_spec.rb +54 -0
  88. data/spec/unit/veritas/sql/generator/relation/visited_spec.rb +35 -0
  89. data/spec/unit/veritas/sql/generator/visitor/class_methods/handler_for_spec.rb +71 -0
  90. data/spec/unit/veritas/sql/generator/visitor/visit_spec.rb +12 -0
  91. data/spec/unit/veritas/sql/generator/visitor/visited_spec.rb +11 -0
  92. data/tasks/quality/ci.rake +2 -0
  93. data/tasks/quality/flay.rake +41 -0
  94. data/tasks/quality/flog.rake +45 -0
  95. data/tasks/quality/heckle.rake +203 -0
  96. data/tasks/quality/metric_fu.rake +26 -0
  97. data/tasks/quality/reek.rake +9 -0
  98. data/tasks/quality/roodi.rake +15 -0
  99. data/tasks/quality/yardstick.rake +23 -0
  100. data/tasks/spec.rake +38 -0
  101. data/tasks/yard.rake +9 -0
  102. data/veritas-sql-generator.gemspec +222 -0
  103. metadata +285 -0
@@ -0,0 +1,111 @@
1
+ # encoding: utf-8
2
+
3
+ module Veritas
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
+ ALL_COLUMNS = '*'.freeze
15
+
16
+ # Return the alias name
17
+ #
18
+ # @return [#to_s]
19
+ #
20
+ # @api private
21
+ attr_reader :name
22
+
23
+ # Factory method to instantiate the generator for the relation
24
+ #
25
+ # @param [Veritas::Relation]
26
+ #
27
+ # @return [Generator::Relation]
28
+ #
29
+ # @api private
30
+ def self.visit(relation)
31
+ klass = case relation
32
+ when Veritas::Relation::Operation::Set then self::Set
33
+ when Veritas::Relation::Operation::Binary then self::Binary
34
+ when Veritas::Relation::Operation::Unary then self::Unary
35
+ when Veritas::BaseRelation then self::Base
36
+ else
37
+ raise InvalidRelationError, "#{relation.class} is not a visitable relation"
38
+ end
39
+ klass.new.visit(relation)
40
+ end
41
+
42
+ # Return the subquery for the relation and identifier
43
+ #
44
+ # @param [#to_subquery] relation
45
+ #
46
+ # @param [#to_s] identifier
47
+ # optional identifier, defaults to relation.name
48
+ #
49
+ # @return [#to_s]
50
+ #
51
+ # @api private
52
+ def self.subquery(relation, identifier = relation.name)
53
+ "(#{relation.to_subquery}) AS #{visit_identifier(identifier)}"
54
+ end
55
+
56
+ # Initialize a Generator
57
+ #
58
+ # @return [undefined]
59
+ #
60
+ # @api private
61
+ def initialize
62
+ @sql = EMPTY_STRING
63
+ end
64
+
65
+ # Visit an object and generate SQL from each node
66
+ #
67
+ # @example
68
+ # generator.visit(visitable)
69
+ #
70
+ # @param [Visitable] visitable
71
+ # A visitable object
72
+ #
73
+ # @return [self]
74
+ #
75
+ # @raise [Visitor::UnknownObject]
76
+ # raised when the visitable object has no handler
77
+ #
78
+ # @api public
79
+ def visit(visitable)
80
+ @sql = dispatch(visitable).to_s.freeze
81
+ freeze
82
+ end
83
+
84
+ # Returns the current SQL string
85
+ #
86
+ # @example
87
+ # sql = generator.to_sql
88
+ #
89
+ # @return [String]
90
+ #
91
+ # @api public
92
+ def to_sql
93
+ @sql
94
+ end
95
+
96
+ # Test if a visitable object has been visited
97
+ #
98
+ # @example
99
+ # visitor.visited? # true or false
100
+ #
101
+ # @return [Boolean]
102
+ #
103
+ # @api public
104
+ def visited?
105
+ !@name.nil?
106
+ end
107
+
108
+ end # class Relation
109
+ end # module Generator
110
+ end # module SQL
111
+ end # module Veritas
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ module Veritas
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 Veritas
@@ -0,0 +1,184 @@
1
+ # encoding: utf-8
2
+
3
+ module Veritas
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
+ # Return the subquery for the generator and identifier
17
+ #
18
+ # @param [#to_subquery] generator
19
+ #
20
+ # @return [#to_s]
21
+ #
22
+ # @api private
23
+ def self.subquery(generator, *)
24
+ generator.kind_of?(Base) ? generator.to_subquery : super
25
+ end
26
+
27
+ # Visit an Join
28
+ #
29
+ # @param [Algebra::Join] join
30
+ #
31
+ # @return [self]
32
+ #
33
+ # @api private
34
+ def visit_veritas_algebra_join(join)
35
+ set_operation(JOIN)
36
+ set_columns(join)
37
+ set_operands(join)
38
+ set_name
39
+ self
40
+ end
41
+
42
+ # Visit an Product
43
+ #
44
+ # @param [Algebra::Product] product
45
+ #
46
+ # @return [self]
47
+ #
48
+ # @api private
49
+ def visit_veritas_algebra_product(product)
50
+ set_operation(PRODUCT)
51
+ set_columns(product)
52
+ set_operands(product)
53
+ set_name
54
+ self
55
+ end
56
+
57
+ # Return the SQL for the binary relation
58
+ #
59
+ # @example
60
+ # sql = binary_relation.to_s
61
+ #
62
+ # @return [#to_s]
63
+ #
64
+ # @api public
65
+ def to_s
66
+ generate_sql(@columns)
67
+ end
68
+
69
+ # Return the SQL suitable for an subquery
70
+ #
71
+ # @return [#to_s]
72
+ #
73
+ # @api private
74
+ def to_subquery
75
+ generate_sql(ALL_COLUMNS)
76
+ end
77
+
78
+ private
79
+
80
+ # Generate the SQL using the supplied columns
81
+ #
82
+ # @param [String] columns
83
+ #
84
+ # @return [#to_s]
85
+ #
86
+ # @api private
87
+ def generate_sql(columns)
88
+ return EMPTY_STRING unless visited?
89
+ "SELECT #{columns} FROM #{left_subquery} #{@operation} #{right_subquery}"
90
+ end
91
+
92
+ # Return the left subquery
93
+ #
94
+ # @return [#to_s]
95
+ #
96
+ # @api private
97
+ def left_subquery
98
+ self.class.subquery(@left, LEFT_NAME)
99
+ end
100
+
101
+ # Return the right subquery
102
+ #
103
+ # @return [#to_s]
104
+ #
105
+ # @api private
106
+ def right_subquery
107
+ self.class.subquery(@right, RIGHT_NAME)
108
+ end
109
+
110
+ # Set the operation
111
+ #
112
+ # @param [#to_s] operation
113
+ #
114
+ # @return [undefined]
115
+ #
116
+ # @api private
117
+ def set_operation(operation)
118
+ @operation = operation
119
+ end
120
+
121
+ # Set the columns from the relation
122
+ #
123
+ # @param [Relation::Operation::Binary] relation
124
+ #
125
+ # @return [undefined]
126
+ #
127
+ # @api private
128
+ def set_columns(relation)
129
+ @columns = columns_for(relation)
130
+ end
131
+
132
+ # Set the operands from the relation
133
+ #
134
+ # @param [Relation::Operation::Binary] relation
135
+ #
136
+ # @return [undefined]
137
+ #
138
+ # @api private
139
+ def set_operands(relation)
140
+ util = self.class
141
+ @left = util.visit(relation.left)
142
+ @right = util.visit(relation.right)
143
+ end
144
+
145
+ # Set the name using the operands' name
146
+ #
147
+ # @return [undefined]
148
+ #
149
+ # @api private
150
+ def set_name
151
+ @name = [ @left.name, @right.name ].uniq.join(UNDERSCORE).freeze
152
+ end
153
+
154
+ # Return a list of columns in a header
155
+ #
156
+ # @param [Veritas::Relation] relation
157
+ #
158
+ # @return [#to_s]
159
+ #
160
+ # @api private
161
+ def columns_for(relation)
162
+ relation.header.map { |attribute| dispatch(attribute) }.join(SEPARATOR)
163
+ end
164
+
165
+ # Generates an SQL statement for base relation binary operands
166
+ class Base < Relation::Base
167
+
168
+ private
169
+
170
+ # Generate the SQL for this base relation
171
+ #
172
+ # @return [#to_s]
173
+ #
174
+ # @api private
175
+ def generate_sql(*)
176
+ visited? ? @from : EMPTY_STRING
177
+ end
178
+
179
+ end # class Base
180
+ end # class Binary
181
+ end # class Relation
182
+ end # module Generator
183
+ end # module SQL
184
+ end # module Veritas
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+
3
+ module Veritas
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
+ # Visit a Union
16
+ #
17
+ # @param [Algebra::Union] union
18
+ #
19
+ # @return [self]
20
+ #
21
+ # @api private
22
+ def visit_veritas_algebra_union(union)
23
+ set_operation(UNION)
24
+ set_operands(union)
25
+ set_name
26
+ self
27
+ end
28
+
29
+ # Visit an Intersection
30
+ #
31
+ # @param [Algebra::Intersection] intersection
32
+ #
33
+ # @return [self]
34
+ #
35
+ # @api private
36
+ def visit_veritas_algebra_intersection(intersection)
37
+ set_operation(INTERSECTION)
38
+ set_operands(intersection)
39
+ set_name
40
+ self
41
+ end
42
+
43
+ # Visit an Difference
44
+ #
45
+ # @param [Algebra::Difference] difference
46
+ #
47
+ # @return [self]
48
+ #
49
+ # @api private
50
+ def visit_veritas_algebra_difference(difference)
51
+ set_operation(DIFFERENCE)
52
+ set_operands(difference)
53
+ set_name
54
+ self
55
+ end
56
+
57
+ # Return the SQL for the set relation
58
+ #
59
+ # @example
60
+ # sql = set_relation.to_s
61
+ #
62
+ # @return [#to_s]
63
+ #
64
+ # @api public
65
+ def to_s
66
+ generate_sql(:to_s)
67
+ end
68
+
69
+ # Return the SQL suitable for an subquery
70
+ #
71
+ # @return [#to_s]
72
+ #
73
+ # @api private
74
+ def to_subquery
75
+ generate_sql(:to_subquery)
76
+ end
77
+
78
+ private
79
+
80
+ # Generate the SQL using the supplied method
81
+ #
82
+ # @param [Symbol] method
83
+ #
84
+ # @return [#to_s]
85
+ #
86
+ # @api private
87
+ def generate_sql(method)
88
+ return EMPTY_STRING unless visited?
89
+ "(#{@left.send(method)}) #{@operation} (#{@right.send(method)})"
90
+ end
91
+
92
+ # Generates an SQL statement for base relation set operands
93
+ class Base < Relation::Base; end
94
+
95
+ end # class Set
96
+ end # class Relation
97
+ end # module Generator
98
+ end # module SQL
99
+ end # module Veritas
@@ -0,0 +1,326 @@
1
+ # encoding: utf-8
2
+
3
+ module Veritas
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, Literal, Logic
12
+
13
+ inheritable_alias(:visit_veritas_relation_operation_reverse => :visit_veritas_relation_operation_order)
14
+
15
+ DISTINCT = 'DISTINCT '.freeze
16
+ COLLAPSIBLE = {
17
+ Algebra::Projection => Set[ Algebra::Projection, Algebra::Restriction, ].freeze,
18
+ Algebra::Restriction => Set[ Algebra::Projection, Veritas::Relation::Operation::Order, Veritas::Relation::Operation::Reverse, ].freeze,
19
+ Veritas::Relation::Operation::Order => Set[ Algebra::Projection, Algebra::Restriction, Veritas::Relation::Operation::Order, Veritas::Relation::Operation::Reverse, Algebra::Rename ].freeze,
20
+ Veritas::Relation::Operation::Reverse => Set[ Algebra::Projection, Algebra::Restriction, Veritas::Relation::Operation::Order, Veritas::Relation::Operation::Reverse, Algebra::Rename ].freeze,
21
+ Veritas::Relation::Operation::Offset => Set[ Algebra::Projection, Algebra::Restriction, Veritas::Relation::Operation::Order, Veritas::Relation::Operation::Reverse, Algebra::Rename ].freeze,
22
+ Veritas::Relation::Operation::Limit => Set[ Algebra::Projection, Algebra::Restriction, Veritas::Relation::Operation::Order, Veritas::Relation::Operation::Reverse, Veritas::Relation::Operation::Offset, Algebra::Rename ].freeze,
23
+ Algebra::Rename => Set[ Algebra::Projection, Algebra::Restriction, Veritas::Relation::Operation::Order, Veritas::Relation::Operation::Reverse, Veritas::Relation::Operation::Offset, Veritas::Relation::Operation::Limit ].freeze,
24
+ }.freeze
25
+
26
+ # Initialize a Unary relation SQL generator
27
+ #
28
+ # @return [undefined]
29
+ #
30
+ # @api private
31
+ def initialize
32
+ super
33
+ @scope = ::Set.new
34
+ end
35
+
36
+ # Visit a Base Relation
37
+ #
38
+ # @param [BaseRelation] base_relation
39
+ #
40
+ # @return [self]
41
+ #
42
+ # @api private
43
+ def visit_veritas_base_relation(base_relation)
44
+ @name = base_relation.name
45
+ @from = visit_identifier(@name)
46
+ @columns = columns_for(base_relation)
47
+ self
48
+ end
49
+
50
+ # Visit a Projection
51
+ #
52
+ # @param [Algebra::Projection] projection
53
+ #
54
+ # @return [self]
55
+ #
56
+ # @api private
57
+ def visit_veritas_algebra_projection(projection)
58
+ @from = subquery_for(projection)
59
+ @distinct = DISTINCT
60
+ @columns = columns_for(projection)
61
+ scope_query(projection)
62
+ self
63
+ end
64
+
65
+ # Visit a Rename
66
+ #
67
+ # @param [Algebra::Rename] rename
68
+ #
69
+ # @return [self]
70
+ #
71
+ # @api private
72
+ def visit_veritas_algebra_rename(rename)
73
+ @from = subquery_for(rename)
74
+ @columns = columns_for(rename.operand, rename.aliases.to_hash)
75
+ scope_query(rename)
76
+ self
77
+ end
78
+
79
+ # Visit a Restriction
80
+ #
81
+ # @param [Algebra::Restriction] restriction
82
+ #
83
+ # @return [self]
84
+ #
85
+ # @api private
86
+ def visit_veritas_algebra_restriction(restriction)
87
+ @from = subquery_for(restriction)
88
+ @where = dispatch(restriction.predicate)
89
+ @columns ||= columns_for(restriction)
90
+ scope_query(restriction)
91
+ self
92
+ end
93
+
94
+ # Visit an Order
95
+ #
96
+ # @param [Relation::Operation::Order] order
97
+ #
98
+ # @return [self]
99
+ #
100
+ # @api private
101
+ def visit_veritas_relation_operation_order(order)
102
+ @from = subquery_for(order)
103
+ @order = order_for(order.directions)
104
+ @columns ||= columns_for(order)
105
+ scope_query(order)
106
+ self
107
+ end
108
+
109
+ # Visit a Limit
110
+ #
111
+ # @param [Relation::Operation::Limit] limit
112
+ #
113
+ # @return [self]
114
+ #
115
+ # @api private
116
+ def visit_veritas_relation_operation_limit(limit)
117
+ @from = subquery_for(limit)
118
+ @limit = limit.limit
119
+ @columns ||= columns_for(limit)
120
+ scope_query(limit)
121
+ self
122
+ end
123
+
124
+ # Visit an Offset
125
+ #
126
+ # @param [Relation::Operation::Offset] offset
127
+ #
128
+ # @return [self]
129
+ #
130
+ # @api private
131
+ def visit_veritas_relation_operation_offset(offset)
132
+ @from = subquery_for(offset)
133
+ @offset = offset.offset
134
+ @columns ||= columns_for(offset)
135
+ scope_query(offset)
136
+ self
137
+ end
138
+
139
+ # Return the SQL for the unary relation
140
+ #
141
+ # @example
142
+ # sql = unary_relation.to_s
143
+ #
144
+ # @return [#to_s]
145
+ #
146
+ # @api public
147
+ def to_s
148
+ generate_sql(@columns)
149
+ end
150
+
151
+ # Return the SQL suitable for an subquery
152
+ #
153
+ # @return [#to_s]
154
+ #
155
+ # @api private
156
+ def to_subquery
157
+ generate_sql(all_columns? ? ALL_COLUMNS : @columns)
158
+ end
159
+
160
+ private
161
+
162
+ # Generate the SQL using the supplied columns
163
+ #
164
+ # @param [String] columns
165
+ #
166
+ # @return [#to_s]
167
+ #
168
+ # @api private
169
+ def generate_sql(columns)
170
+ return EMPTY_STRING unless visited?
171
+ sql = "SELECT #{@distinct}#{columns} FROM #{@from}"
172
+ sql << " WHERE #{@where}" if @where
173
+ sql << " ORDER BY #{@order}" if @order
174
+ sql << " LIMIT #{@limit}" if @limit
175
+ sql << " OFFSET #{@offset}" if @offset
176
+ sql
177
+ end
178
+
179
+ # Return a list of columns in a header
180
+ #
181
+ # @param [Veritas::Relation] relation
182
+ #
183
+ # @param [#[]] aliases
184
+ # optional aliases for the columns
185
+ #
186
+ # @return [#to_s]
187
+ #
188
+ # @api private
189
+ def columns_for(relation, aliases = {})
190
+ relation.header.map { |attribute| column_for(attribute, aliases) }.join(SEPARATOR)
191
+ end
192
+
193
+ # Return the column for an attribute
194
+ #
195
+ # @param [Attribute] attribute
196
+ #
197
+ # @param [#[]] aliases
198
+ # aliases for the columns
199
+ #
200
+ # @return [#to_s]
201
+ #
202
+ # @api private
203
+ def column_for(attribute, aliases)
204
+ column = dispatch(attribute)
205
+ if aliases.key?(attribute)
206
+ alias_for(column, aliases[attribute])
207
+ else
208
+ column
209
+ end
210
+ end
211
+
212
+ # Return the column alias for an attribute
213
+ #
214
+ # @param [#to_s] column
215
+ #
216
+ # @param [Attribute, nil] alias_attribute
217
+ # attribute to use for the alias
218
+ #
219
+ # @return [#to_s]
220
+ #
221
+ # @api private
222
+ def alias_for(column, alias_attribute)
223
+ "#{column} AS #{visit_identifier(alias_attribute.name)}"
224
+ end
225
+
226
+ # Return a list of columns for ordering
227
+ #
228
+ # @param [DirectionSet] directions
229
+ #
230
+ # @return [#to_s]
231
+ #
232
+ # @api private
233
+ def order_for(directions)
234
+ directions.map { |direction| dispatch(direction) }.join(SEPARATOR)
235
+ end
236
+
237
+ # Return an expression that can be used for the FROM
238
+ #
239
+ # @param [Relation] relation
240
+ #
241
+ # @return [#to_s]
242
+ #
243
+ # @api private
244
+ def subquery_for(relation)
245
+ operand = relation.operand
246
+ subquery = dispatch(operand)
247
+ if collapse_subquery_for?(relation)
248
+ @from
249
+ else
250
+ aliased_subquery(subquery)
251
+ end
252
+ end
253
+
254
+ # Add the operand to the current scope
255
+ #
256
+ # @param [Relation] operand
257
+ #
258
+ # @return [undefined]
259
+ #
260
+ # @api private
261
+ def scope_query(operand)
262
+ @scope << operand.class
263
+ end
264
+
265
+ # Test if the query should use "*" and not specify columns explicitly
266
+ #
267
+ # @return [Boolean]
268
+ #
269
+ # @api private
270
+ def all_columns?
271
+ !@scope.include?(Algebra::Projection) && !@scope.include?(Algebra::Rename)
272
+ end
273
+
274
+ # Test if the relation should be collapsed
275
+ #
276
+ # @param [Relation] relation
277
+ #
278
+ # @return [#to_s]
279
+ #
280
+ # @api private
281
+ def collapse_subquery_for?(relation)
282
+ @scope.subset?(COLLAPSIBLE.fetch(relation.class))
283
+ end
284
+
285
+ # Returns an aliased subquery
286
+ #
287
+ # @param [#to_s] subquery
288
+ #
289
+ # @return [#to_s]
290
+ #
291
+ # @api private
292
+ def aliased_subquery(subquery)
293
+ self.class.subquery(subquery)
294
+ ensure
295
+ reset_query_state
296
+ end
297
+
298
+ # Visit a Binary Relation
299
+ #
300
+ # @param [Relation::Operation::Binary] set
301
+ #
302
+ # @return [Relation::Binary]
303
+ #
304
+ # @api private
305
+ def visit_veritas_relation_operation_binary(binary)
306
+ generator = self.class.visit(binary)
307
+ @name = generator.name
308
+ @from = aliased_subquery(generator)
309
+ generator
310
+ end
311
+
312
+ # Reset the query state
313
+ #
314
+ # @return [undefined]
315
+ #
316
+ # @api private
317
+ def reset_query_state
318
+ @scope.clear
319
+ @distinct = @columns = @where = @order = @limit = @offset = nil
320
+ end
321
+
322
+ end # class Unary
323
+ end # class Relation
324
+ end # module Generator
325
+ end # module SQL
326
+ end # module Veritas