veritas-sql-generator 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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