lib/axiom/sql/generator/function/predicate.rb
# encoding: utf-8
module Axiom
module SQL
module Generator
module Function
# Generates an SQL statement for a predicate function
module Predicate
include Function
EQUAL_TO = '='.freeze
EQUAL_TO_NULL = 'IS'.freeze
NOT_EQUAL_TO = '<>'.freeze
NOT_EQUAL_TO_NULL = 'IS NOT'.freeze
GREATER_THAN = '>'.freeze
GREATER_THAN_OR_EQUAL_TO = '>='.freeze
LESS_THAN = '<'.freeze
LESS_THAN_OR_EQUAL_TO = '<='.freeze
IN = 'IN'.freeze
NOT_IN = 'NOT IN'.freeze
BETWEEN = 'BETWEEN'.freeze
NOT_BETWEEN = 'NOT BETWEEN'.freeze
EMPTY_ARRAY = [].freeze
# Visit an Equality predicate
#
# @param [Function::Predicate::Equality] equality
#
# @return [#to_s]
#
# @api private
def visit_axiom_function_predicate_equality(equality)
binary_infix_operation_sql(equality.right.nil? ? EQUAL_TO_NULL : EQUAL_TO, equality)
end
# Visit an Inequality predicate
#
# @param [Function::Predicate::Inequality] inequality
#
# @return [#to_s]
#
# @api private
def visit_axiom_function_predicate_inequality(inequality)
expressions = inequality_expressions(inequality)
expressions.one? ? expressions.first : Generator.parenthesize!(expressions.join(' OR '))
end
# Visit an GreaterThan predicate
#
# @param [Function::Predicate::GreaterThan] greater_than
#
# @return [#to_s]
#
# @api private
def visit_axiom_function_predicate_greater_than(greater_than)
binary_infix_operation_sql(GREATER_THAN, greater_than)
end
# Visit an GreaterThanOrEqualTo predicate
#
# @param [Function::Predicate::GreaterThanOrEqualTo] greater_than_or_equal_to
#
# @return [#to_s]
#
# @api private
def visit_axiom_function_predicate_greater_than_or_equal_to(greater_than_or_equal_to)
binary_infix_operation_sql(GREATER_THAN_OR_EQUAL_TO, greater_than_or_equal_to)
end
# Visit an LessThan predicate
#
# @param [Function::Predicate::LessThan] less_than
#
# @return [#to_s]
#
# @api private
def visit_axiom_function_predicate_less_than(less_than)
binary_infix_operation_sql(LESS_THAN, less_than)
end
# Visit an LessThanOrEqualTo predicate
#
# @param [Function::Predicate::LessThanOrEqualTo] less_than_or_equal_to
#
# @return [#to_s]
#
# @api private
def visit_axiom_function_predicate_less_than_or_equal_to(less_than_or_equal_to)
binary_infix_operation_sql(LESS_THAN_OR_EQUAL_TO, less_than_or_equal_to)
end
# Visit an Inclusion predicate
#
# @param [Function::Predicate::Inclusion] inclusion
#
# @return [#to_s]
#
# @api private
def visit_axiom_function_predicate_inclusion(inclusion)
case inclusion.right
when Range then range_inclusion_sql(inclusion)
when EMPTY_ARRAY then FALSE
else
binary_infix_operation_sql(IN, inclusion)
end
end
# Visit an Exclusion predicate
#
# @param [Function::Predicate::Exclusion] exclusion
#
# @return [#to_s]
#
# @api private
def visit_axiom_function_predicate_exclusion(exclusion)
case exclusion.right
when Range then range_exclusion_sql(exclusion)
when EMPTY_ARRAY then TRUE
else
binary_infix_operation_sql(NOT_IN, exclusion)
end
end
private
# Return the SQL for an Inclusion using a Range
#
# @param [Function::Predicate::Inclusion] inclusion
#
# @return [#to_s]
#
# @api private
def range_inclusion_sql(inclusion)
if inclusion.right.exclude_end?
exclusive_range_inclusion_sql(inclusion)
else
inclusive_range_sql(BETWEEN, inclusion)
end
end
# Return the SQL for an Exclusion using a Range
#
# @param [Function::Predicate::Exclusion] exclusion
#
# @return [#to_s]
#
# @api private
def range_exclusion_sql(exclusion)
if exclusion.right.exclude_end?
exclusive_range_exclusion_sql(exclusion)
else
inclusive_range_sql(NOT_BETWEEN, exclusion)
end
end
# Return the SQL for an Inclusion using an exclusive Range
#
# @param [Function::Predicate::Inclusion] inclusion
#
# @return [#to_s]
#
# @api private
def exclusive_range_inclusion_sql(inclusion)
left = new_from_enumerable_predicate(Axiom::Function::Predicate::GreaterThanOrEqualTo, inclusion, :first)
right = new_from_enumerable_predicate(Axiom::Function::Predicate::LessThan, inclusion, :last)
dispatch left.and(right)
end
# Return the SQL for an Exclusion using an exclusive Range
#
# @param [Function::Predicate::Exclusion] exclusion
#
# @return [#to_s]
#
# @api private
def exclusive_range_exclusion_sql(exclusion)
left = new_from_enumerable_predicate(Axiom::Function::Predicate::LessThan, exclusion, :first)
right = new_from_enumerable_predicate(Axiom::Function::Predicate::GreaterThanOrEqualTo, exclusion, :last)
dispatch left.or(right)
end
# Instantiate a new Predicate object from an Enumerable Predicate
#
# @param [Class<Function::Predicate>] klass
# the type of predicate to create
# @param [Function::Predicate::Enumerable] predicate
# the enumerable predicate
# @param [Symbol] method
# the method to call on the right operand of the predicate
# @return [Function::Predicate]
#
# @api private
def new_from_enumerable_predicate(klass, predicate, method)
klass.new(predicate.left, predicate.right.send(method))
end
# Return the expressions for an inequality
#
# @param [Function::Predicate::Inequality] inequality
#
# @return [Array<#to_s>]
#
# @api private
def inequality_expressions(inequality)
expressions = [
inequality_sql(inequality),
optional_is_null_sql(inequality.left),
optional_is_null_sql(inequality.right),
]
expressions.compact!
expressions
end
# Return the SQL for an inequality predicate
#
# @param [Function::Predicate::Inequality] inequality
#
# @return [#to_s]
#
# @api private
def inequality_sql(inequality)
binary_infix_operation_sql(inequality.right.nil? ? NOT_EQUAL_TO_NULL : NOT_EQUAL_TO, inequality)
end
# Return the SQL for an operation using an inclusive Range
#
# @param [#to_s] operator
#
# @param [Function::Predicate::Enumerable] predicate
#
# @return [#to_s]
#
# @api private
def inclusive_range_sql(operator, predicate)
right = predicate.right
"#{dispatch(predicate.left)} #{operator} #{dispatch(right.first)} AND #{dispatch(right.last)}"
end
# Return SQL for an Equality with a nil value for optional attributes
#
# @param [Attribute] attribute
#
# @return [#to_sql, nil]
#
# @api private
def optional_is_null_sql(attribute)
dispatch(attribute.eq(nil)) if optional?(attribute)
end
# Test if the object is not required
#
# @param [Object] operand
#
# @return [Boolean]
#
# @api private
def optional?(operand)
operand.respond_to?(:required?) && !operand.required?
end
end # module Predicate
end # module Function
end # module Generator
end # module SQL
end # module Axiom