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.
- data/Gemfile +33 -0
- data/LICENSE +20 -0
- data/README.rdoc +27 -0
- data/Rakefile +25 -0
- data/TODO +17 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/roodi.yml +16 -0
- data/config/site.reek +124 -0
- data/config/yardstick.yml +2 -0
- data/lib/veritas-sql-generator.rb +3 -0
- data/lib/veritas/base_relation.rb +36 -0
- data/lib/veritas/sql/generator.rb +35 -0
- data/lib/veritas/sql/generator/attribute.rb +25 -0
- data/lib/veritas/sql/generator/direction.rb +36 -0
- data/lib/veritas/sql/generator/identifier.rb +27 -0
- data/lib/veritas/sql/generator/literal.rb +160 -0
- data/lib/veritas/sql/generator/logic.rb +349 -0
- data/lib/veritas/sql/generator/relation.rb +111 -0
- data/lib/veritas/sql/generator/relation/base.rb +14 -0
- data/lib/veritas/sql/generator/relation/binary.rb +184 -0
- data/lib/veritas/sql/generator/relation/set.rb +99 -0
- data/lib/veritas/sql/generator/relation/unary.rb +326 -0
- data/lib/veritas/sql/generator/version.rb +9 -0
- data/lib/veritas/sql/generator/visitor.rb +121 -0
- data/spec/rcov.opts +6 -0
- data/spec/shared/command_method_behavior.rb +7 -0
- data/spec/shared/generated_sql_behavior.rb +15 -0
- data/spec/shared/idempotent_method_behavior.rb +7 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/unit/veritas/base_relation/name_spec.rb +45 -0
- data/spec/unit/veritas/sql/generator/attribute/visit_veritas_attribute_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/direction/visit_veritas_relation_operation_order_ascending_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/direction/visit_veritas_relation_operation_order_descending_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/identifier/visit_identifier_spec.rb +26 -0
- data/spec/unit/veritas/sql/generator/literal/class_methods/dup_frozen_spec.rb +23 -0
- data/spec/unit/veritas/sql/generator/literal/visit_class_spec.rb +31 -0
- data/spec/unit/veritas/sql/generator/literal/visit_date_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/literal/visit_date_time_spec.rb +61 -0
- data/spec/unit/veritas/sql/generator/literal/visit_enumerable_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/literal/visit_false_class_spec.rb +14 -0
- data/spec/unit/veritas/sql/generator/literal/visit_nil_class_spec.rb +14 -0
- data/spec/unit/veritas/sql/generator/literal/visit_numeric_spec.rb +34 -0
- data/spec/unit/veritas/sql/generator/literal/visit_string_spec.rb +26 -0
- data/spec/unit/veritas/sql/generator/literal/visit_time_spec.rb +97 -0
- data/spec/unit/veritas/sql/generator/literal/visit_true_class_spec.rb +14 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_connective_conjunction_spec.rb +16 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_connective_disjunction_spec.rb +16 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_connective_negation_spec.rb +16 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_equality_spec.rb +27 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_exclusion_spec.rb +43 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_greater_than_or_equal_to_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_greater_than_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_inclusion_spec.rb +43 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_inequality_spec.rb +55 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_less_than_or_equal_to_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_predicate_less_than_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_proposition_contradiction_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/logic/visit_veritas_logic_proposition_tautology_spec.rb +15 -0
- data/spec/unit/veritas/sql/generator/relation/binary/base/to_subquery_spec.rb +35 -0
- data/spec/unit/veritas/sql/generator/relation/binary/base/visit_veritas_base_relation_spec.rb +22 -0
- data/spec/unit/veritas/sql/generator/relation/binary/class_methods/subquery_spec.rb +42 -0
- data/spec/unit/veritas/sql/generator/relation/binary/to_s_spec.rb +35 -0
- data/spec/unit/veritas/sql/generator/relation/binary/to_subquery_spec.rb +35 -0
- data/spec/unit/veritas/sql/generator/relation/binary/visit_veritas_algebra_join_spec.rb +138 -0
- data/spec/unit/veritas/sql/generator/relation/binary/visit_veritas_algebra_product_spec.rb +139 -0
- data/spec/unit/veritas/sql/generator/relation/class_methods/subquery_spec.rb +33 -0
- data/spec/unit/veritas/sql/generator/relation/class_methods/visit_spec.rb +61 -0
- data/spec/unit/veritas/sql/generator/relation/name_spec.rb +30 -0
- data/spec/unit/veritas/sql/generator/relation/set/to_s_spec.rb +55 -0
- data/spec/unit/veritas/sql/generator/relation/set/to_subquery_spec.rb +55 -0
- data/spec/unit/veritas/sql/generator/relation/set/visit_veritas_algebra_difference_spec.rb +138 -0
- data/spec/unit/veritas/sql/generator/relation/set/visit_veritas_algebra_intersection_spec.rb +138 -0
- data/spec/unit/veritas/sql/generator/relation/set/visit_veritas_algebra_union_spec.rb +138 -0
- data/spec/unit/veritas/sql/generator/relation/to_sql_spec.rb +52 -0
- data/spec/unit/veritas/sql/generator/relation/unary/to_s_spec.rb +55 -0
- data/spec/unit/veritas/sql/generator/relation/unary/to_subquery_spec.rb +75 -0
- data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_algebra_projection_spec.rb +138 -0
- data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_algebra_rename_spec.rb +136 -0
- data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_algebra_restriction_spec.rb +157 -0
- data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_base_relation_spec.rb +21 -0
- data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_relation_operation_limit_spec.rb +125 -0
- data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_relation_operation_offset_spec.rb +125 -0
- data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_relation_operation_order_spec.rb +136 -0
- data/spec/unit/veritas/sql/generator/relation/unary/visit_veritas_relation_operation_reverse_spec.rb +125 -0
- data/spec/unit/veritas/sql/generator/relation/visit_spec.rb +54 -0
- data/spec/unit/veritas/sql/generator/relation/visited_spec.rb +35 -0
- data/spec/unit/veritas/sql/generator/visitor/class_methods/handler_for_spec.rb +71 -0
- data/spec/unit/veritas/sql/generator/visitor/visit_spec.rb +12 -0
- data/spec/unit/veritas/sql/generator/visitor/visited_spec.rb +11 -0
- data/tasks/quality/ci.rake +2 -0
- data/tasks/quality/flay.rake +41 -0
- data/tasks/quality/flog.rake +45 -0
- data/tasks/quality/heckle.rake +203 -0
- data/tasks/quality/metric_fu.rake +26 -0
- data/tasks/quality/reek.rake +9 -0
- data/tasks/quality/roodi.rake +15 -0
- data/tasks/quality/yardstick.rake +23 -0
- data/tasks/spec.rake +38 -0
- data/tasks/yard.rake +9 -0
- data/veritas-sql-generator.gemspec +222 -0
- 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
|