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