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.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +37 -0
- data/.rspec +4 -0
- data/.rvmrc +1 -0
- data/.travis.yml +35 -0
- data/CONTRIBUTING.md +11 -0
- data/Gemfile +8 -0
- data/Gemfile.devtools +57 -0
- data/Guardfile +23 -0
- data/LICENSE +20 -0
- data/README.md +70 -0
- data/Rakefile +5 -0
- data/TODO +34 -0
- data/axiom-sql-generator.gemspec +25 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +165 -0
- data/config/yardstick.yml +2 -0
- data/lib/axiom-sql-generator.rb +3 -0
- data/lib/axiom/sql/generator.rb +61 -0
- data/lib/axiom/sql/generator/attribute.rb +25 -0
- data/lib/axiom/sql/generator/core_ext/date.rb +20 -0
- data/lib/axiom/sql/generator/core_ext/date_time.rb +46 -0
- data/lib/axiom/sql/generator/direction.rb +38 -0
- data/lib/axiom/sql/generator/function.rb +55 -0
- data/lib/axiom/sql/generator/function/aggregate.rb +134 -0
- data/lib/axiom/sql/generator/function/connective.rb +53 -0
- data/lib/axiom/sql/generator/function/numeric.rb +135 -0
- data/lib/axiom/sql/generator/function/predicate.rb +266 -0
- data/lib/axiom/sql/generator/function/proposition.rb +38 -0
- data/lib/axiom/sql/generator/function/string.rb +29 -0
- data/lib/axiom/sql/generator/identifier.rb +28 -0
- data/lib/axiom/sql/generator/literal.rb +157 -0
- data/lib/axiom/sql/generator/relation.rb +240 -0
- data/lib/axiom/sql/generator/relation/base.rb +14 -0
- data/lib/axiom/sql/generator/relation/binary.rb +136 -0
- data/lib/axiom/sql/generator/relation/insertion.rb +62 -0
- data/lib/axiom/sql/generator/relation/materialized.rb +60 -0
- data/lib/axiom/sql/generator/relation/set.rb +107 -0
- data/lib/axiom/sql/generator/relation/unary.rb +379 -0
- data/lib/axiom/sql/generator/version.rb +12 -0
- data/lib/axiom/sql/generator/visitor.rb +121 -0
- data/spec/rcov.opts +7 -0
- data/spec/shared/generated_sql_behavior.rb +15 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/config_alias.rb +3 -0
- data/spec/unit/axiom/sql/generator/attribute/visit_axiom_attribute_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/class_methods/parenthesize_spec.rb +18 -0
- data/spec/unit/axiom/sql/generator/direction/visit_axiom_relation_operation_order_ascending_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/direction/visit_axiom_relation_operation_order_descending_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_count_spec.rb +16 -0
- data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_maximum_spec.rb +16 -0
- data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_mean_spec.rb +16 -0
- data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_minimum_spec.rb +16 -0
- data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_standard_deviation_spec.rb +16 -0
- data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_sum_spec.rb +16 -0
- data/spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_variance_spec.rb +16 -0
- data/spec/unit/axiom/sql/generator/function/connective/visit_axiom_function_connective_conjunction_spec.rb +20 -0
- data/spec/unit/axiom/sql/generator/function/connective/visit_axiom_function_connective_disjunction_spec.rb +20 -0
- data/spec/unit/axiom/sql/generator/function/connective/visit_axiom_function_connective_negation_spec.rb +20 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_absolute_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_addition_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_division_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_exponentiation_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_modulo_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_multiplication_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_square_root_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_subtraction_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_unary_minus_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_unary_plus_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_equality_spec.rb +27 -0
- data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_exclusion_spec.rb +47 -0
- data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_greater_than_or_equal_to_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_greater_than_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_inclusion_spec.rb +47 -0
- data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_inequality_spec.rb +55 -0
- data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_less_than_or_equal_to_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_less_than_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/proposition/visit_axiom_function_proposition_contradiction_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/proposition/visit_axiom_function_proposition_tautology_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/function/string/visit_axiom_function_string_length_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/identifier/visit_identifier_spec.rb +26 -0
- data/spec/unit/axiom/sql/generator/literal/class_methods/dup_frozen_spec.rb +23 -0
- data/spec/unit/axiom/sql/generator/literal/visit_class_spec.rb +31 -0
- data/spec/unit/axiom/sql/generator/literal/visit_date_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/literal/visit_date_time_spec.rb +55 -0
- data/spec/unit/axiom/sql/generator/literal/visit_enumerable_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/literal/visit_false_class_spec.rb +14 -0
- data/spec/unit/axiom/sql/generator/literal/visit_nil_class_spec.rb +14 -0
- data/spec/unit/axiom/sql/generator/literal/visit_numeric_spec.rb +34 -0
- data/spec/unit/axiom/sql/generator/literal/visit_string_spec.rb +26 -0
- data/spec/unit/axiom/sql/generator/literal/visit_time_spec.rb +97 -0
- data/spec/unit/axiom/sql/generator/literal/visit_true_class_spec.rb +14 -0
- data/spec/unit/axiom/sql/generator/relation/binary/base/to_subquery_spec.rb +35 -0
- data/spec/unit/axiom/sql/generator/relation/binary/base/visit_axiom_relation_base_spec.rb +22 -0
- data/spec/unit/axiom/sql/generator/relation/binary/to_s_spec.rb +35 -0
- data/spec/unit/axiom/sql/generator/relation/binary/to_subquery_spec.rb +35 -0
- data/spec/unit/axiom/sql/generator/relation/binary/visit_axiom_algebra_join_spec.rb +179 -0
- data/spec/unit/axiom/sql/generator/relation/binary/visit_axiom_algebra_product_spec.rb +183 -0
- data/spec/unit/axiom/sql/generator/relation/class_methods/visit_spec.rb +71 -0
- data/spec/unit/axiom/sql/generator/relation/insertion/to_subquery_spec.rb +15 -0
- data/spec/unit/axiom/sql/generator/relation/insertion/visit_axiom_relation_operation_insertion_spec.rb +187 -0
- data/spec/unit/axiom/sql/generator/relation/materialized/visit_axiom_relation_materialized_spec.rb +28 -0
- data/spec/unit/axiom/sql/generator/relation/materialized/visited_spec.rb +26 -0
- data/spec/unit/axiom/sql/generator/relation/name_spec.rb +30 -0
- data/spec/unit/axiom/sql/generator/relation/set/class_methods/normalize_operand_headers_spec.rb +35 -0
- data/spec/unit/axiom/sql/generator/relation/set/to_s_spec.rb +55 -0
- data/spec/unit/axiom/sql/generator/relation/set/to_subquery_spec.rb +55 -0
- data/spec/unit/axiom/sql/generator/relation/set/visit_axiom_algebra_difference_spec.rb +191 -0
- data/spec/unit/axiom/sql/generator/relation/set/visit_axiom_algebra_intersection_spec.rb +188 -0
- data/spec/unit/axiom/sql/generator/relation/set/visit_axiom_algebra_union_spec.rb +188 -0
- data/spec/unit/axiom/sql/generator/relation/to_s_spec.rb +50 -0
- data/spec/unit/axiom/sql/generator/relation/to_sql_spec.rb +52 -0
- data/spec/unit/axiom/sql/generator/relation/to_subquery_spec.rb +49 -0
- data/spec/unit/axiom/sql/generator/relation/unary/to_s_spec.rb +55 -0
- data/spec/unit/axiom/sql/generator/relation/unary/to_subquery_spec.rb +75 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_extension_spec.rb +165 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_projection_spec.rb +193 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_rename_spec.rb +178 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_restriction_spec.rb +199 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_summarization_spec.rb +652 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_base_spec.rb +21 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_limit_spec.rb +165 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_offset_spec.rb +165 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_order_spec.rb +183 -0
- data/spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_reverse_spec.rb +165 -0
- data/spec/unit/axiom/sql/generator/relation/visit_spec.rb +54 -0
- data/spec/unit/axiom/sql/generator/relation/visited_spec.rb +35 -0
- data/spec/unit/axiom/sql/generator/visitor/class_methods/handler_for_spec.rb +71 -0
- data/spec/unit/axiom/sql/generator/visitor/visit_spec.rb +12 -0
- data/spec/unit/axiom/sql/generator/visitor/visited_spec.rb +11 -0
- data/spec/unit/date/iso8601_spec.rb +23 -0
- data/spec/unit/date_time/iso8601_spec.rb +112 -0
- metadata +325 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Axiom
|
|
4
|
+
module SQL
|
|
5
|
+
module Generator
|
|
6
|
+
|
|
7
|
+
# Generates an SQL statement for an identifier
|
|
8
|
+
module Identifier
|
|
9
|
+
|
|
10
|
+
QUOTE = '"'.freeze
|
|
11
|
+
ESCAPED_QUOTE = '""'.freeze
|
|
12
|
+
|
|
13
|
+
# Quote the identifier
|
|
14
|
+
#
|
|
15
|
+
# @param [#to_s] identifier
|
|
16
|
+
#
|
|
17
|
+
# @return [#to_s]
|
|
18
|
+
#
|
|
19
|
+
# @api private
|
|
20
|
+
def visit_identifier(identifier)
|
|
21
|
+
escaped = identifier.to_s.gsub(QUOTE, ESCAPED_QUOTE)
|
|
22
|
+
escaped.insert(0, QUOTE) << QUOTE
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end # module Identifier
|
|
26
|
+
end # module Generator
|
|
27
|
+
end # module SQL
|
|
28
|
+
end # module Axiom
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Axiom
|
|
4
|
+
module SQL
|
|
5
|
+
module Generator
|
|
6
|
+
|
|
7
|
+
# Generates an SQL statement for a literal
|
|
8
|
+
module Literal
|
|
9
|
+
|
|
10
|
+
TRUE = 'TRUE'.freeze
|
|
11
|
+
FALSE = 'FALSE'.freeze
|
|
12
|
+
NULL = 'NULL'.freeze
|
|
13
|
+
QUOTE = "'".freeze
|
|
14
|
+
ESCAPED_QUOTE = "''".freeze
|
|
15
|
+
SEPARATOR = ', '.freeze
|
|
16
|
+
TIME_SCALE = 9
|
|
17
|
+
|
|
18
|
+
# Returns an unfrozen object
|
|
19
|
+
#
|
|
20
|
+
# Some objects, like Date, DateTime and Time memoize values
|
|
21
|
+
# when serialized to a String, so when they are frozen this will
|
|
22
|
+
# dup them and then return the unfrozen copy.
|
|
23
|
+
#
|
|
24
|
+
# @param [Object] object
|
|
25
|
+
#
|
|
26
|
+
# @return [Object]
|
|
27
|
+
# non-frozen object
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
def self.dup_frozen(object)
|
|
31
|
+
object.frozen? ? object.dup : object
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Visit an Enumerable
|
|
35
|
+
#
|
|
36
|
+
# @param [Enumerable] enumerable
|
|
37
|
+
#
|
|
38
|
+
# @return [#to_s]
|
|
39
|
+
#
|
|
40
|
+
# @api private
|
|
41
|
+
def visit_enumerable(enumerable)
|
|
42
|
+
Generator.parenthesize!(
|
|
43
|
+
enumerable.map { |entry| dispatch entry }.join(SEPARATOR)
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Visit a String
|
|
48
|
+
#
|
|
49
|
+
# @note The string must be UTF-8 encoded
|
|
50
|
+
#
|
|
51
|
+
# @param [String] string
|
|
52
|
+
#
|
|
53
|
+
# @return [#to_s]
|
|
54
|
+
#
|
|
55
|
+
# @api private
|
|
56
|
+
def visit_string(string)
|
|
57
|
+
escaped = string.gsub(QUOTE, ESCAPED_QUOTE)
|
|
58
|
+
escaped.insert(0, QUOTE) << QUOTE
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Visit a Numeric
|
|
62
|
+
#
|
|
63
|
+
# @param [Numeric] numeric
|
|
64
|
+
#
|
|
65
|
+
# @return [#to_s]
|
|
66
|
+
#
|
|
67
|
+
# @api private
|
|
68
|
+
def visit_numeric(numeric)
|
|
69
|
+
numeric.to_s
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Visit a Class
|
|
73
|
+
#
|
|
74
|
+
# @param [Class] klass
|
|
75
|
+
#
|
|
76
|
+
# @return [#to_s]
|
|
77
|
+
#
|
|
78
|
+
# @api private
|
|
79
|
+
def visit_class(klass)
|
|
80
|
+
name = klass.name.to_s
|
|
81
|
+
name.empty? ? NULL : visit_string(name)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Visit a Date and return in ISO 8601 date format
|
|
85
|
+
#
|
|
86
|
+
# @param [Date] date
|
|
87
|
+
#
|
|
88
|
+
# @return [#to_s]
|
|
89
|
+
#
|
|
90
|
+
# @api private
|
|
91
|
+
def visit_date(date)
|
|
92
|
+
dispatch date.iso8601
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Visit a DateTime and return in ISO 8601 date-time format
|
|
96
|
+
#
|
|
97
|
+
# Converts the DateTime to UTC format.
|
|
98
|
+
#
|
|
99
|
+
# @param [DateTime] date_time
|
|
100
|
+
#
|
|
101
|
+
# @return [#to_s]
|
|
102
|
+
#
|
|
103
|
+
# @api private
|
|
104
|
+
def visit_date_time(date_time)
|
|
105
|
+
dispatch date_time.new_offset.iso8601(TIME_SCALE)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Visit a Time
|
|
109
|
+
#
|
|
110
|
+
# Converts the Time to UTC format.
|
|
111
|
+
#
|
|
112
|
+
# @param [Time] time
|
|
113
|
+
#
|
|
114
|
+
# @return [#to_s]
|
|
115
|
+
#
|
|
116
|
+
# @api private
|
|
117
|
+
def visit_time(time)
|
|
118
|
+
dispatch Literal.dup_frozen(time).utc.iso8601(TIME_SCALE)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Visit a true value
|
|
122
|
+
#
|
|
123
|
+
# @param [true] _true
|
|
124
|
+
#
|
|
125
|
+
# @return [#to_s]
|
|
126
|
+
#
|
|
127
|
+
# @api private
|
|
128
|
+
def visit_true_class(_true)
|
|
129
|
+
TRUE
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Visit a false value
|
|
133
|
+
#
|
|
134
|
+
# @param [false] _false
|
|
135
|
+
#
|
|
136
|
+
# @return [#to_s]
|
|
137
|
+
#
|
|
138
|
+
# @api private
|
|
139
|
+
def visit_false_class(_false)
|
|
140
|
+
FALSE
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Visit a nil value
|
|
144
|
+
#
|
|
145
|
+
# @param [nil] _nil
|
|
146
|
+
#
|
|
147
|
+
# @return [#to_s]
|
|
148
|
+
#
|
|
149
|
+
# @api private
|
|
150
|
+
def visit_nil_class(_nil)
|
|
151
|
+
NULL
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end # module Literal
|
|
155
|
+
end # module Generator
|
|
156
|
+
end # module SQL
|
|
157
|
+
end # module Axiom
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Axiom
|
|
4
|
+
module SQL
|
|
5
|
+
module Generator
|
|
6
|
+
|
|
7
|
+
# Abstract base class for SQL generation from a relation
|
|
8
|
+
class Relation < Visitor
|
|
9
|
+
extend Identifier
|
|
10
|
+
include Attribute
|
|
11
|
+
|
|
12
|
+
EMPTY_STRING = ''.freeze
|
|
13
|
+
SEPARATOR = ', '.freeze
|
|
14
|
+
STAR = '*'.freeze
|
|
15
|
+
EMPTY_HASH = {}.freeze
|
|
16
|
+
|
|
17
|
+
# Return the alias name
|
|
18
|
+
#
|
|
19
|
+
# @return [#to_s]
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
attr_reader :name
|
|
23
|
+
|
|
24
|
+
# Factory method to instantiate the generator for the relation
|
|
25
|
+
#
|
|
26
|
+
# @param [Axiom::Relation] relation
|
|
27
|
+
#
|
|
28
|
+
# @return [Generator::Relation]
|
|
29
|
+
#
|
|
30
|
+
# @api private
|
|
31
|
+
def self.visit(relation)
|
|
32
|
+
klass = case relation
|
|
33
|
+
when Axiom::Relation::Operation::Insertion then self::Insertion
|
|
34
|
+
when Axiom::Relation::Operation::Set then self::Set
|
|
35
|
+
when Axiom::Relation::Operation::Binary then self::Binary
|
|
36
|
+
when Axiom::Relation::Operation::Unary then self::Unary
|
|
37
|
+
when Axiom::Relation::Base then self::Base
|
|
38
|
+
when Axiom::Relation::Materialized then self::Materialized
|
|
39
|
+
else
|
|
40
|
+
raise InvalidRelationError, "#{relation.class} is not a visitable relation"
|
|
41
|
+
end
|
|
42
|
+
klass.new.visit(relation)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Initialize a Generator
|
|
46
|
+
#
|
|
47
|
+
# @return [undefined]
|
|
48
|
+
#
|
|
49
|
+
# @api private
|
|
50
|
+
def initialize
|
|
51
|
+
@sql = EMPTY_STRING
|
|
52
|
+
@extensions = {}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Visit an object and generate SQL from each node
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
# generator.visit(visitable)
|
|
59
|
+
#
|
|
60
|
+
# @param [Visitable] visitable
|
|
61
|
+
# A visitable object
|
|
62
|
+
#
|
|
63
|
+
# @return [self]
|
|
64
|
+
#
|
|
65
|
+
# @raise [Visitor::UnknownObject]
|
|
66
|
+
# raised when the visitable object has no handler
|
|
67
|
+
#
|
|
68
|
+
# @api public
|
|
69
|
+
def visit(visitable)
|
|
70
|
+
@sql = dispatch(visitable).to_s.freeze
|
|
71
|
+
freeze
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns the current SQL string
|
|
75
|
+
#
|
|
76
|
+
# @example
|
|
77
|
+
# sql = generator.to_sql
|
|
78
|
+
#
|
|
79
|
+
# @return [String]
|
|
80
|
+
#
|
|
81
|
+
# @api public
|
|
82
|
+
def to_sql
|
|
83
|
+
@sql
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Return the SQL for the unary relation
|
|
87
|
+
#
|
|
88
|
+
# @example
|
|
89
|
+
# sql = unary_relation.to_s
|
|
90
|
+
#
|
|
91
|
+
# @return [#to_s]
|
|
92
|
+
#
|
|
93
|
+
# @api public
|
|
94
|
+
def to_s
|
|
95
|
+
return EMPTY_STRING unless visited?
|
|
96
|
+
generate_sql(query_columns)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Return the SQL suitable for an subquery
|
|
100
|
+
#
|
|
101
|
+
# @return [#to_s]
|
|
102
|
+
#
|
|
103
|
+
# @api private
|
|
104
|
+
def to_subquery
|
|
105
|
+
return EMPTY_STRING unless visited?
|
|
106
|
+
Generator.parenthesize!(generate_sql(subquery_columns))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Test if a visitable object has been visited
|
|
110
|
+
#
|
|
111
|
+
# @example
|
|
112
|
+
# visitor.visited? # true or false
|
|
113
|
+
#
|
|
114
|
+
# @return [Boolean]
|
|
115
|
+
#
|
|
116
|
+
# @api public
|
|
117
|
+
def visited?
|
|
118
|
+
instance_variable_defined?(:@name)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
# Return the columns to use in a query
|
|
124
|
+
#
|
|
125
|
+
# @return [#to_s]
|
|
126
|
+
#
|
|
127
|
+
# @api private
|
|
128
|
+
def query_columns
|
|
129
|
+
explicit_columns
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Return the columns to use in a subquery
|
|
133
|
+
#
|
|
134
|
+
# @return [#to_s]
|
|
135
|
+
#
|
|
136
|
+
# @api private
|
|
137
|
+
def subquery_columns
|
|
138
|
+
implicit_columns
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Return the implicit columns for the select list
|
|
142
|
+
#
|
|
143
|
+
# @return [#to_s]
|
|
144
|
+
#
|
|
145
|
+
# @api private
|
|
146
|
+
def implicit_columns
|
|
147
|
+
sql = [ STAR, column_list_for(@extensions) ]
|
|
148
|
+
sql.reject! { |fragment| fragment.empty? }
|
|
149
|
+
sql.join(SEPARATOR)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Return the explicit columns for the select list
|
|
153
|
+
#
|
|
154
|
+
# @return [#to_s]
|
|
155
|
+
#
|
|
156
|
+
# @api private
|
|
157
|
+
def explicit_columns
|
|
158
|
+
@distinct.to_s + column_list_for(@extensions.merge(@columns || EMPTY_HASH))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Return the list of columns
|
|
162
|
+
#
|
|
163
|
+
# @param [#map] columns
|
|
164
|
+
#
|
|
165
|
+
# @return [#to_s]
|
|
166
|
+
#
|
|
167
|
+
# @api private
|
|
168
|
+
def column_list_for(columns)
|
|
169
|
+
sql = columns.values_at(*@header)
|
|
170
|
+
sql.compact!
|
|
171
|
+
sql.join(SEPARATOR)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Return a list of columns in a header
|
|
175
|
+
#
|
|
176
|
+
# @param [Axiom::Relation] relation
|
|
177
|
+
#
|
|
178
|
+
# @param [#[]] aliases
|
|
179
|
+
# optional aliases for the columns
|
|
180
|
+
#
|
|
181
|
+
# @return [Hash]
|
|
182
|
+
#
|
|
183
|
+
# @api private
|
|
184
|
+
def columns_for(relation, aliases = EMPTY_HASH)
|
|
185
|
+
columns = {}
|
|
186
|
+
relation.header.each do |attribute|
|
|
187
|
+
columns[aliases.fetch(attribute, attribute)] = column_for(attribute, aliases)
|
|
188
|
+
end
|
|
189
|
+
columns
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Return the column for an attribute
|
|
193
|
+
#
|
|
194
|
+
# @param [Attribute] attribute
|
|
195
|
+
#
|
|
196
|
+
# @param [#[]] aliases
|
|
197
|
+
# aliases for the columns
|
|
198
|
+
#
|
|
199
|
+
# @return [#to_s]
|
|
200
|
+
#
|
|
201
|
+
# @api private
|
|
202
|
+
def column_for(attribute, aliases)
|
|
203
|
+
if aliases.key?(attribute)
|
|
204
|
+
alias_for(attribute, aliases[attribute])
|
|
205
|
+
else
|
|
206
|
+
dispatch(attribute)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Return the column alias for an attribute
|
|
211
|
+
#
|
|
212
|
+
# @param [#to_s] attribute
|
|
213
|
+
#
|
|
214
|
+
# @param [Attribute, nil] alias_attribute
|
|
215
|
+
# attribute to use for the alias
|
|
216
|
+
#
|
|
217
|
+
# @return [#to_s]
|
|
218
|
+
#
|
|
219
|
+
# @api private
|
|
220
|
+
def alias_for(attribute, alias_attribute)
|
|
221
|
+
"#{dispatch(attribute)} AS #{dispatch(alias_attribute)}"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Add extensions for extension and summarize queries
|
|
225
|
+
#
|
|
226
|
+
# @param [#each] extensions
|
|
227
|
+
#
|
|
228
|
+
# @return [undefined]
|
|
229
|
+
#
|
|
230
|
+
# @api private
|
|
231
|
+
def add_extensions(extensions)
|
|
232
|
+
extensions.each do |attribute, function|
|
|
233
|
+
@extensions[attribute] = alias_for(function, attribute)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
end # class Relation
|
|
238
|
+
end # module Generator
|
|
239
|
+
end # module SQL
|
|
240
|
+
end # module Axiom
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Axiom
|
|
4
|
+
module SQL
|
|
5
|
+
module Generator
|
|
6
|
+
class Relation
|
|
7
|
+
|
|
8
|
+
# Generates an SQL statement for a base relation
|
|
9
|
+
class Base < Unary; end
|
|
10
|
+
|
|
11
|
+
end # class Relation
|
|
12
|
+
end # module Generator
|
|
13
|
+
end # module SQL
|
|
14
|
+
end # module Axiom
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Axiom
|
|
4
|
+
module SQL
|
|
5
|
+
module Generator
|
|
6
|
+
class Relation
|
|
7
|
+
|
|
8
|
+
# Generates an SQL statement for a Binary relation
|
|
9
|
+
class Binary < Relation
|
|
10
|
+
|
|
11
|
+
JOIN = 'NATURAL JOIN'.freeze
|
|
12
|
+
PRODUCT = 'CROSS JOIN'.freeze
|
|
13
|
+
LEFT_NAME = 'left'.freeze
|
|
14
|
+
RIGHT_NAME = 'right'.freeze
|
|
15
|
+
|
|
16
|
+
# Visit an Join
|
|
17
|
+
#
|
|
18
|
+
# @param [Algebra::Join] join
|
|
19
|
+
#
|
|
20
|
+
# @return [self]
|
|
21
|
+
#
|
|
22
|
+
# @api private
|
|
23
|
+
def visit_axiom_algebra_join(join)
|
|
24
|
+
@header = join.header
|
|
25
|
+
set_operation(JOIN)
|
|
26
|
+
set_columns(join)
|
|
27
|
+
set_operands(join)
|
|
28
|
+
set_name
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Visit an Product
|
|
33
|
+
#
|
|
34
|
+
# @param [Algebra::Product] product
|
|
35
|
+
#
|
|
36
|
+
# @return [self]
|
|
37
|
+
#
|
|
38
|
+
# @api private
|
|
39
|
+
def visit_axiom_algebra_product(product)
|
|
40
|
+
@header = product.header
|
|
41
|
+
set_operation(PRODUCT)
|
|
42
|
+
set_columns(product)
|
|
43
|
+
set_operands(product)
|
|
44
|
+
set_name
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Generate the SQL using the supplied columns
|
|
51
|
+
#
|
|
52
|
+
# @param [String] columns
|
|
53
|
+
#
|
|
54
|
+
# @return [#to_s]
|
|
55
|
+
#
|
|
56
|
+
# @api private
|
|
57
|
+
def generate_sql(columns)
|
|
58
|
+
"SELECT #{columns} FROM #{@left.to_subquery} AS #{visit_identifier(LEFT_NAME)} #{@operation} #{@right.to_subquery} AS #{visit_identifier(RIGHT_NAME)}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Set the operation
|
|
62
|
+
#
|
|
63
|
+
# @param [#to_s] operation
|
|
64
|
+
#
|
|
65
|
+
# @return [undefined]
|
|
66
|
+
#
|
|
67
|
+
# @api private
|
|
68
|
+
def set_operation(operation)
|
|
69
|
+
@operation = operation
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Set the columns from the relation
|
|
73
|
+
#
|
|
74
|
+
# @param [Relation::Operation::Binary] relation
|
|
75
|
+
#
|
|
76
|
+
# @return [undefined]
|
|
77
|
+
#
|
|
78
|
+
# @api private
|
|
79
|
+
def set_columns(relation)
|
|
80
|
+
@columns = columns_for(relation)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Set the operands from the relation
|
|
84
|
+
#
|
|
85
|
+
# @param [Relation::Operation::Binary] relation
|
|
86
|
+
#
|
|
87
|
+
# @return [undefined]
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
90
|
+
def set_operands(relation)
|
|
91
|
+
util = self.class
|
|
92
|
+
@left = util.visit(relation.left)
|
|
93
|
+
@right = util.visit(relation.right)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Set the name using the operands' name
|
|
97
|
+
#
|
|
98
|
+
# @return [undefined]
|
|
99
|
+
#
|
|
100
|
+
# @api private
|
|
101
|
+
def set_name
|
|
102
|
+
@name = [ @left.name, @right.name ].uniq.join(UNDERSCORE).freeze
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Generates an SQL statement for base relation binary operands
|
|
106
|
+
class Base < Relation::Base
|
|
107
|
+
|
|
108
|
+
# Return the SQL suitable for an subquery
|
|
109
|
+
#
|
|
110
|
+
# Does not parenthesize the query
|
|
111
|
+
#
|
|
112
|
+
# @return [#to_s]
|
|
113
|
+
#
|
|
114
|
+
# @api private
|
|
115
|
+
def to_subquery
|
|
116
|
+
return EMPTY_STRING unless visited?
|
|
117
|
+
generate_sql
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# Generate the SQL for this base relation
|
|
123
|
+
#
|
|
124
|
+
# @return [#to_s]
|
|
125
|
+
#
|
|
126
|
+
# @api private
|
|
127
|
+
def generate_sql(*)
|
|
128
|
+
@from
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end # class Base
|
|
132
|
+
end # class Binary
|
|
133
|
+
end # class Relation
|
|
134
|
+
end # module Generator
|
|
135
|
+
end # module SQL
|
|
136
|
+
end # module Axiom
|