lib/axiom/optimizer/algebra/rename.rb
# encoding: utf-8
module Axiom
class Optimizer
module Algebra
# Abstract base class representing Rename optimizations
class Rename < Relation::Operation::Unary
# The optimized aliases
#
# @return [Rename::Aliases]
#
# @api private
attr_reader :aliases
# Initialize an Rename optimizer
#
# @param [Relation] operation
#
# @return [undefined]
#
# @api private
def initialize(operation)
super
@aliases = operation.aliases
end
private
# Wrap the operand's operand in a Rename
#
# @return [Rename]
#
# @api private
def wrap_operand(operand = operand.operand)
operand.rename(aliases)
end
# Optimize when the operand is a Rename
class RenameOperand < self
# Test if the operand is a Rename
#
# @return [Boolean]
#
# @api private
def optimizable?
operand.kind_of?(operation.class)
end
# Flatten nested Renames into a single Rename
#
# @return [Projection]
#
# @api private
def optimize
wrap_operand
end
private
# The optimized aliases
#
# @return [Rename::Aliases]
#
# @api private
def aliases
super.union(operand.aliases)
end
end # class RenameOperand
# Optimize when the operand is a Rename with aliases that cancel out
class RenameOperandAndEmptyAliases < RenameOperand
# Test if the operand is a Rename with aliases that cancel out
#
# @return [Boolean]
#
# @api private
def optimizable?
super && aliases.empty?
end
# A Rename wrapping a Rename with aliases that cancel out is a noop
#
# @return [Relation]
#
# @api private
def optimize
operand.operand
end
end # class RenameOperandAndEmptyAliases
# Optimize when the operand is a Projection
class ProjectionOperand < self
# Test if the operand is a Projection
#
# @return [Boolean]
#
# @api private
def optimizable?
operand.kind_of?(Axiom::Algebra::Projection) && distributive?
end
# Wrap the Rename in a Projection
#
# @return [Projection]
#
# @api private
def optimize
wrap_operand.project(header)
end
private
# Test if the rename can be distributed over the projection
#
# @return [Boolean]
#
# @api private
def distributive?
names = alias_names
removed_attributes.none? do |attribute|
names.include?(attribute.name)
end
end
# Return the aliases as an inverted Hash
#
# @return [Hash]
#
# @api private
def alias_names
aliases.to_hash.values.map(&:name)
end
# Returns the attributes removed from the projection
#
# @return [#all?]
#
# @api private
def removed_attributes
operand.operand.header - operand.header
end
end # class ProjectionOperand
# Optimize when the operand is a Restriction
class RestrictionOperand < self
# Test if the operand is a Restriction
#
# @return [Boolean]
#
# @api private
def optimizable?
operand.kind_of?(Axiom::Algebra::Restriction)
end
# Wrap the Rename in a Restriction
#
# @return [Restriction]
#
# @api private
def optimize
wrap_operand.restrict(rename_predicate)
end
private
# Rename the operand predicate
#
# @return [Function]
#
# @api private
def rename_predicate
operand.predicate.rename(aliases)
end
end # class RestrictionOperand
# Optimize when the operand is a Set
class SetOperand < self
# Test if the operand is a Set
#
# @return [Boolean]
#
# @api private
def optimizable?
operand.kind_of?(Axiom::Relation::Operation::Set)
end
# Wrap each operand in the Set in a Rename
#
# @return [Set]
#
# @api private
def optimize
operand.class.new(wrap_left, wrap_right)
end
private
# Utility method to wrap the left operand in a Rename
#
# @return [Rename]
#
# @api private
def wrap_left
wrap_operand(operand.left)
end
# Utility method to wrap the right operand in a Rename
#
# @return [Rename]
#
# @api private
def wrap_right
wrap_operand(operand.right)
end
end # class SetOperand
# Optimize when the operand is a Reverse
class ReverseOperand < self
# Test if the operand is a Reverse
#
# @return [Boolean]
#
# @api private
def optimizable?
operand.kind_of?(Axiom::Relation::Operation::Reverse)
end
# Wrap the Rename in a Reverse
#
# @return [Reverse]
#
# @api private
def optimize
wrap_operand.reverse
end
end # class ReverseOperand
# Optimize when the operand is an Sorted
class SortedOperand < self
include Relation::Operation::Unary::SortedOperand
# Wrap the Rename in an Sorted
#
# @return [Sorted]
#
# @api private
def optimize
wrap_operand.sort_by(directions)
end
private
# Return the renamed directions
#
# @return [Relation::Operation::Sorted::DirectionSet]
#
# @api private
def directions
operand.directions.rename(aliases)
end
end # class SortedOperand
# Optimize when the operand is a Limit
class LimitOperand < self
# Test if the operand is a Limit
#
# @return [Boolean]
#
# @api private
def optimizable?
operand.kind_of?(Axiom::Relation::Operation::Limit)
end
# Wrap the Rename in a Limit
#
# @return [Limit]
#
# @api private
def optimize
wrap_operand.take(operand.limit)
end
end # class LimitOperand
# Optimize when the operand is an Offset
class OffsetOperand < self
# Test if the operand is an Offset
#
# @return [Boolean]
#
# @api private
def optimizable?
operand.kind_of?(Axiom::Relation::Operation::Offset)
end
# Wrap the Rename in an Offset
#
# @return [Offset]
#
# @api private
def optimize
wrap_operand.drop(operand.offset)
end
end # class OffsetOperand
# Optimize when the operand is Empty
class EmptyOperand < self
# Test if the operand is empty
#
# @return [Boolean]
#
# @api private
def optimizable?
operand.kind_of?(Axiom::Relation::Empty)
end
# Return a new Empty relation with the operation's headers
#
# @return [Empty]
#
# @api private
def optimize
Axiom::Relation::Empty.new(header)
end
end # class EmptyOperand
# Optimize when operand is optimizable
class UnoptimizedOperand < self
include Function::Unary::UnoptimizedOperand
# Return a Rename with an optimized operand
#
# @return [Rename]
#
# @api private
def optimize
wrap_operand(operand)
end
end # class UnoptimizedOperand
Axiom::Algebra::Rename.optimizer = chain(
UnchangedHeader,
RenameOperandAndEmptyAliases,
RenameOperand,
ProjectionOperand,
RestrictionOperand,
SetOperand,
ReverseOperand,
SortedOperand,
LimitOperand,
OffsetOperand,
EmptyOperand,
MaterializedOperand,
UnoptimizedOperand
)
end # class Rename
end # module Algebra
end # class Optimizer
end # module Axiom