Commits

Kirill Simonov committed e9a000a

Refactoring functions.

Added an abstract class `Formula` as a mixin for respective
binding, code and phrase subclasses. Replaced core expression
nodes with formula nodes.

Comments (0)

Files changed (17)

src/htsql/tr/assemble.py

 from ..adapter import Adapter, adapts, adapts_many
 from ..domain import BooleanDomain
 from .coerce import coerce
-from .code import (Code, LiteralCode, EqualityCode, TotalEqualityCode,
-                   ConjunctionCode, DisjunctionCode, NegationCode,
-                   FormulaCode, CastCode, Unit, ColumnUnit)
+from .code import (Code, LiteralCode, FormulaCode, CastCode, Unit, ColumnUnit)
 from .term import (PreTerm, Term, UnaryTerm, BinaryTerm, TableTerm,
                    ScalarTerm, FilterTerm, JoinTerm, CorrelationTerm,
                    EmbeddingTerm, ProjectionTerm, OrderTerm, SegmentTerm,
 from .frame import (LeafFrame, ScalarFrame, TableFrame, BranchFrame,
                     NestedFrame, SegmentFrame, QueryFrame,
                     Phrase, LiteralPhrase, NullPhrase, TruePhrase, FalsePhrase,
-                    EqualityPhrase, TotalEqualityPhrase, CastPhrase,
-                    ConjunctionPhrase, DisjunctionPhrase, NegationPhrase,
-                    ColumnPhrase, ReferencePhrase, EmbeddingPhrase,
+                    CastPhrase, ColumnPhrase, ReferencePhrase, EmbeddingPhrase,
                     FormulaPhrase, Anchor, LeadingAnchor)
-from .signature import Signature
+from .signature import (Signature, IsEqualSig, IsTotallyEqualSig, IsNullSig,
+                        NullIfSig, IfNullSig, AndSig)
 
 
 class Claim(Comparable, Printable):
         for lop, rop in self.term.joints:
             lop = self.state.evaluate(lop, router=self.term.lkid)
             rop = self.state.evaluate(rop, router=self.term.rkid)
-            equality = EqualityPhrase(lop, rop, self.term.expression)
+            is_nullable = (lop.is_nullable or rop.is_nullable)
+            equality = FormulaPhrase(IsEqualSig(+1), coerce(BooleanDomain()),
+                                     is_nullable, self.term.expression,
+                                     lop=lop, rop=rop)
             equalities.append(equality)
         condition = None
         if equalities:
-            condition = ConjunctionPhrase(equalities, self.term.expression)
+            is_nullable = any(equality.is_nullable for equality in equalities)
+            condition = FormulaPhrase(AndSig(), coerce(BooleanDomain()),
+                                      is_nullable, self.term.expression,
+                                      ops=equalities)
         elif self.term.is_left or self.term.is_right:
             condition = TruePhrase(self.term.expression)
         # Generate a `JOIN` clause for the second subframe.
             # Evaluate the right operand against the child frame.
             rop = self.state.evaluate(rop, router=self.term.kid)
             # An individual condition.
-            equality = EqualityPhrase(lop, rop, self.term.expression)
+            is_nullable = (lop.is_nullable or rop.is_nullable)
+            equality = FormulaPhrase(IsEqualSig(+1), coerce(BooleanDomain()),
+                                     is_nullable, self.term.expression,
+                                     lop=lop, rop=rop)
             equalities.append(equality)
         # Generate and return the clause.
         condition = None
         if equalities:
-            condition = ConjunctionPhrase(equalities, self.term.expression)
+            is_nullable = any(equality.is_nullable for equality in equalities)
+            condition = FormulaPhrase(AndSig(), coerce(BooleanDomain()),
+                                      is_nullable, self.term.expression,
+                                      ops=equalities)
         return condition
 
 
         return LiteralPhrase(self.code.value, self.code.domain, self.code)
 
 
-class EvaluateEquality(Evaluate):
-    """
-    Evaluates an equality (``=``) code.
-    """
-
-    adapts(EqualityCode)
-
-    # FIXME: a generic translator for the cases when we just need to evaluate
-    # the child nodes and generate a new phrase with the same structure as
-    # the original code node.
-
-    def __call__(self):
-        # Evaluate the operands and generate a phrase node.
-        lop = self.state.evaluate(self.code.lop)
-        rop = self.state.evaluate(self.code.rop)
-        return EqualityPhrase(lop, rop, self.code)
-
-
-class EvaluateTotalEquality(Evaluate):
-    """
-    Evaluates a total equality (``==``) code.
-    """
-
-    adapts(TotalEqualityCode)
-
-    def __call__(self):
-        # Evaluate the operands and generate a phrase node.
-        lop = self.state.evaluate(self.code.lop)
-        rop = self.state.evaluate(self.code.rop)
-        return TotalEqualityPhrase(lop, rop, self.code)
-
-
-class EvaluateConjunction(Evaluate):
-    """
-    Evaluates a logical "AND" (``&``) code.
-    """
-
-    adapts(ConjunctionCode)
-
-    def __call__(self):
-        # Evaluate the operands and generate a phrase node.
-        ops = [self.state.evaluate(op) for op in self.code.ops]
-        return ConjunctionPhrase(ops, self.code)
-
-
-class EvaluateDisjunction(Evaluate):
-    """
-    Evaluates a logical "OR" (``|``) code.
-    """
-
-    adapts(DisjunctionCode)
-
-    def __call__(self):
-        # Evaluate the operands and generate a phrase node.
-        ops = [self.state.evaluate(op) for op in self.code.ops]
-        return DisjunctionPhrase(ops, self.code)
-
-
-class EvaluateNegation(Evaluate):
-    """
-    Evaluates a logical "NOT" (``!``) code.
-    """
-
-    adapts(NegationCode)
-
-    def __call__(self):
-        # Evaluate the operand and generate a phrase node.
-        op = self.state.evaluate(self.code.op)
-        return NegationPhrase(op, self.code)
-
-
 class EvaluateCast(Evaluate):
     """
     Evaluates a cast code.
         return CastPhrase(base, self.code.domain, base.is_nullable, self.code)
 
 
+class EvaluateFormula(Evaluate):
+
+    adapts(FormulaCode)
+
+    def __call__(self):
+        evaluate = EvaluateBySignature(self.code, self.state)
+        return evaluate()
+
+
 class EvaluateBySignature(Adapter):
 
     adapts(Signature)
 
-    is_null_regular = True
-    is_nullable = True
-
     @classmethod
     def dispatch(interface, code, *args, **kwds):
         assert isinstance(code, FormulaCode)
 
     def __call__(self):
         arguments = self.arguments.map(self.state.evaluate)
-        if self.is_null_regular:
-            is_nullable = any(cell.is_nullable for cell in arguments.cells())
-        else:
-            is_nullable = self.is_nullable
+        is_nullable = any(cell.is_nullable for cell in arguments.cells())
         return FormulaPhrase(self.signature,
                              self.domain,
                              is_nullable,
                              **arguments)
 
 
-class EvaluateFormula(Evaluate):
+class EvaluateIsTotallyEqual(EvaluateBySignature):
 
-    adapts(FormulaCode)
+    adapts(IsTotallyEqualSig)
 
     def __call__(self):
-        evaluate = EvaluateBySignature(self.code, self.state)
-        return evaluate()
+        arguments = self.arguments.map(self.state.evaluate)
+        return FormulaPhrase(self.signature, self.domain, False, self.code,
+                             **arguments)
+
+
+class EvaluateIsNull(EvaluateBySignature):
+
+    adapts(IsNullSig)
+
+    def __call__(self):
+        arguments = self.arguments.map(self.state.evaluate)
+        return FormulaPhrase(self.signature, self.domain, False, self.code,
+                             **arguments)
+
+
+class EvaluateNullIf(EvaluateBySignature):
+
+    adapts(NullIfSig)
+
+    def __call__(self):
+        arguments = self.arguments.map(self.state.evaluate)
+        return FormulaPhrase(self.signature, self.domain, True, self.code,
+                             **arguments)
+
+
+class EvaluateIfNull(EvaluateBySignature):
+
+    adapts(IfNullSig)
+
+    def __call__(self):
+        arguments = self.arguments.map(self.state.evaluate)
+        is_nullable = all(cell.is_nullable for cell in arguments.cells())
+        return FormulaPhrase(self.signature, self.domain, is_nullable,
+                             self.code, **arguments)
 
 
 class EvaluateUnit(Evaluate):

src/htsql/tr/binding.py

 from ..domain import Domain, VoidDomain, BooleanDomain, TupleDomain
 from .syntax import Syntax
 from .coerce import coerce
-from .signature import Signature, Bag
+from .signature import Signature, Bag, Formula
 
 
 class Binding(Clonable, Printable):
         self.rop = rop
 
 
-class EqualityBinding(EqualityBindingBase):
-    """
-    Represents the "equality" (``=``) operator.
-
-    The regular "equality" operator treats ``NULL`` as a special value:
-    if any of the arguments is ``NULL``, the result is also ``NULL``.
-    """
-
-
-class TotalEqualityBinding(EqualityBindingBase):
-    """
-    Represents the "total equality" (``==``) operator.
-
-    The "total equality" operator treats ``NULL`` as a regular value.
-    """
-
-
-class ConnectiveBindingBase(Binding):
-    """
-    Represents an N-ary logical connective.
-
-    This is an abstract class for "AND" (``&``) and "OR" (``|``) operators.
-
-    `ops` (a list of :class:`Binding`)
-        The operands.
-    """
-
-    def __init__(self, ops, syntax):
-        assert isinstance(ops, listof(Binding))
-        # Use the engine-specific Boolean type, which, we assume,
-        # must always exist.
-        domain = coerce(BooleanDomain())
-        assert domain is not None
-        super(ConnectiveBindingBase, self).__init__(domain, syntax)
-        self.ops = ops
-
-
-class ConjunctionBinding(ConnectiveBindingBase):
-    """
-    Represents the logical "AND" (``&``) operator.
-    """
-
-
-class DisjunctionBinding(ConnectiveBindingBase):
-    """
-    Represents the logical "OR" (``|``) operator.
-    """
-
-
-class NegationBinding(Binding):
-    """
-    Represents the logical "NOT" (``!``) operator.
-
-    `op` (:class:`Binding`)
-        The operand.
-    """
-
-    def __init__(self, op, syntax):
-        assert isinstance(op, Binding)
-        # Use the engine-specific Boolean type, which, we assume,
-        # must always exist.
-        domain = coerce(BooleanDomain())
-        assert domain is not None
-        super(NegationBinding, self).__init__(domain, syntax)
-        self.op = op
-
-
 class CastBinding(Binding):
     """
     Represents a type conversion operator.
         self.base = base
 
 
-class FormulaBinding(Binding):
+class FormulaBinding(Formula, Binding):
     """
     Represents a function or an operator binding.
 
         A mapping from argument names to values.
     """
 
-    # FIXME: should logical operators (`EqualityBinding`, etc) inherit
-    # from `FunctionBinding`?
-
     def __init__(self, signature, domain, syntax, **arguments):
         assert isinstance(signature, Signature)
         arguments = Bag(**arguments)
         assert arguments.admits(Binding, signature)
-        super(FormulaBinding, self).__init__(domain, syntax)
-        self.signature = signature
-        self.arguments = arguments
-        arguments.impress(self)
+        super(FormulaBinding, self).__init__(signature, arguments,
+                                             domain, syntax)
 
 
 class WrapperBinding(Binding):

src/htsql/tr/code.py

 from .syntax import IdentifierSyntax
 from .binding import Binding, QueryBinding, SegmentBinding
 from .coerce import coerce
-from .signature import Signature, Bag
+from .signature import Signature, Bag, Formula
 
 
 class Expression(Comparable, Clonable, Printable):
         return repr(self.value)
 
 
-class EqualityCodeBase(Code):
-    """
-    Represents an equality operator.
-
-    This is an abstract class for the ``=`` and ``==`` operators.
-
-    `lop` (:class:`Code`)
-        The left operand.
-
-    `rop` (:class:`Code`)
-        The right operand.
-    """
-
-    def __init__(self, lop, rop, binding):
-        assert isinstance(lop, Code)
-        assert isinstance(rop, Code)
-        super(EqualityCodeBase, self).__init__(
-                    # We rely on that an engine-specific Boolean type
-                    # always exists.
-                    domain=coerce(BooleanDomain()),
-                    # Units of a complex expression are usually a composition
-                    # of argument units.  Note that duplicates are possible.
-                    units=(lop.units+rop.units),
-                    binding=binding,
-                    equality_vector=(lop, rop))
-        self.lop = lop
-        self.rop = rop
-
-
-class EqualityCode(EqualityCodeBase):
-    """
-    Represents the "equality" (``=``) operator.
-
-    The regular "equality" operator treats ``NULL`` as a special value:
-    if any of the arguments is ``NULL``, the result is also ``NULL``.
-    """
-
-
-class TotalEqualityCode(EqualityCodeBase):
-    """
-    Represents the "total equality" (``==``) operator.
-
-    The "total equality" operator treats ``NULL`` as a regular value.
-    """
-
-
-class ConnectiveCodeBase(Code):
-    """
-    Represents an N-ary logical connective.
-
-    This is an abstract class for "AND" (``&``) and "OR" (``|``) operators.
-
-    `ops` (a list of :class:`Code`)
-        The operands.
-    """
-
-    def __init__(self, ops, binding):
-        assert isinstance(ops, listof(Code))
-        assert all(isinstance(op.domain, BooleanDomain) for op in ops)
-        # Extract all units from the arguments.
-        units = []
-        for op in ops:
-            units.extend(op.units)
-        super(ConnectiveCodeBase, self).__init__(
-                    # We rely on that an engine-specific Boolean type
-                    # always exists.
-                    domain=coerce(BooleanDomain()),
-                    units=units,
-                    binding=binding,
-                    equality_vector=tuple(ops))
-        self.ops = ops
-
-
-class ConjunctionCode(ConnectiveCodeBase):
-    """
-    Represents the logical "AND" (``&``) operator.
-    """
-
-
-class DisjunctionCode(ConnectiveCodeBase):
-    """
-    Represents the logical "OR" (``|``) operator.
-    """
-
-
-class NegationCode(Code):
-    """
-    Represents the logical "NOT" (``!``) operator.
-
-    `op` (:class:`Code`)
-        The operand.
-    """
-
-    def __init__(self, op, binding):
-        assert isinstance(op, Code)
-        assert isinstance(op.domain, BooleanDomain)
-        super(NegationCode, self).__init__(
-                    # We rely on that an engine-specific Boolean type
-                    # always exists.
-                    domain=coerce(BooleanDomain()),
-                    units=op.units,
-                    binding=binding,
-                    equality_vector=(op,))
-        self.op = op
-
-
 class CastCode(Code):
     """
     Represents a type conversion operator.
         self.base = base
 
 
-class FormulaCode(Code):
+class FormulaCode(Formula, Code):
     """
     Represents a function or an operator expression.
 
             units.extend(cell.units)
         equality_vector = (signature, domain, arguments.freeze())
         super(FormulaCode, self).__init__(
+                    signature, arguments,
                     domain=domain,
                     units=units,
                     binding=binding,
                     equality_vector=equality_vector)
-        self.signature = signature
-        self.arguments = arguments
-        # For convenience, we permit access to function arguments using
-        # object attributes.
-        arguments.impress(self)
 
 
 class Unit(Code):

src/htsql/tr/dump.py

 from .syntax import IdentifierSyntax, CallSyntax, LiteralSyntax
 from .frame import (Clause, Frame, LeafFrame, ScalarFrame, TableFrame,
                     BranchFrame, NestedFrame, SegmentFrame, QueryFrame,
-                    Phrase, NullPhrase, EqualityPhrase, InequalityPhrase,
-                    TotalEqualityPhrase, TotalInequalityPhrase,
-                    ConjunctionPhrase, DisjunctionPhrase, NegationPhrase,
-                    IsNullPhrase, IsNotNullPhrase, IfNullPhrase, NullIfPhrase,
-                    CastPhrase, LiteralPhrase, ColumnPhrase, ReferencePhrase,
-                    EmbeddingPhrase, FormulaPhrase, Anchor, LeadingAnchor)
-from .signature import Signature
+                    Phrase, NullPhrase, CastPhrase, LiteralPhrase,
+                    ColumnPhrase, ReferencePhrase, EmbeddingPhrase,
+                    FormulaPhrase, Anchor, LeadingAnchor)
+from .signature import (Signature, IsEqualSig, IsTotallyEqualSig, IsInSig,
+                        IsNullSig, IfNullSig, NullIfSig, CompareSig,
+                        AndSig, OrSig, NotSig)
 from .plan import Plan
 import decimal
 import StringIO
         self.state.format("{value:literal}", value=self.value)
 
 
-class DumpEquality(Dump):
-
-    adapts(EqualityPhrase)
-
-    def __call__(self):
-        self.state.format("({lop} = {rop})", self.phrase)
-
-
-class DumpInequality(Dump):
-
-    adapts(InequalityPhrase)
-
-    def __call__(self):
-        self.state.format("({lop} <> {rop})", self.phrase)
-
-
-class DumpTotalEquality(Dump):
-
-    adapts(TotalEqualityPhrase)
-
-    def __call__(self):
-        self.state.format("((CASE WHEN (({lop} = {rop}) OR"
-                          " (({lop} IS NULL) AND ({rop} IS NULL)))"
-                          " THEN 1 ELSE 0 END) = 1)",
-                          self.phrase)
-
-
-class DumpTotalInequality(Dump):
-
-    adapts(TotalInequalityPhrase)
-
-    def __call__(self):
-        self.state.format("((CASE WHEN (({lop} = {rop}) OR"
-                          " (({lop} IS NULL) AND ({rop} IS NULL)))"
-                          " THEN 1 ELSE 0 END) = 0)",
-                          self.phrase)
-
-
-class DumpConjunction(Dump):
-
-    adapts(ConjunctionPhrase)
-
-    def __call__(self):
-        self.state.format("({ops:join{ AND }})", self.phrase)
-
-
-class DumpDisjunction(Dump):
-
-    adapts(DisjunctionPhrase)
-
-    def __call__(self):
-        self.state.format("({ops:join{ OR }})", self.phrase)
-
-
-class DumpNegation(Dump):
-
-    adapts(NegationPhrase)
-
-    def __call__(self):
-        self.state.format("(NOT {op})", self.phrase)
-
-
-class DumpIsNull(Dump):
-
-    adapts(IsNullPhrase)
-
-    def __call__(self):
-        self.state.format("({op} IS NULL)", self.phrase)
-
-
-class DumpIsNotNull(Dump):
-
-    adapts(IsNotNullPhrase)
-
-    def __call__(self):
-        self.state.format("({op} IS NOT NULL)", self.phrase)
-
-
-class DumpIfNull(Dump):
-
-    adapts(IfNullPhrase)
-
-    def __call__(self):
-        self.state.format("COALESCE({lop}, {rop})", self.phrase)
-
-
-class DumpNullIf(Dump):
-
-    adapts(NullIfPhrase)
-
-    def __call__(self):
-        self.state.format("NULLIF({lop}, {rop})", self.phrase)
-
-
 class DumpCast(Dump):
 
     adapts(CastPhrase)
         raise NotImplementedError()
 
 
+class DumpIsEqual(DumpBySignature):
+
+    adapts(IsEqualSig)
+
+    def __call__(self):
+        if self.signature.polarity > 0:
+            self.state.format("({lop} = {rop})", self.arguments)
+        else:
+            self.state.format("({lop} <> {rop})", self.arguments)
+
+
+class DumpIsTotallyEqual(DumpBySignature):
+
+    adapts(IsTotallyEqualSig)
+
+    def __call__(self):
+        if self.signature.polarity > 0:
+            self.state.format("(CASE WHEN (({lop} = {rop}) OR"
+                              " (({lop} IS NULL) AND ({rop} IS NULL)))"
+                              " THEN 1 ELSE 0 END)",
+                              self.arguments)
+        else:
+            self.state.format("(CASE WHEN (({lop} <> {rop}) AND"
+                              " (({lop} IS NOT NULL) OR ({rop} IS NOT NULL)))"
+                              " THEN 1 ELSE 0 END)",
+                              self.arguments)
+
+
+class DumpIsIn(DumpBySignature):
+
+    adapts(IsInSig)
+
+    def __call__(self):
+        self.state.format("({lop} {polarity:polarity}IN ({rops:join{, }}))",
+                          self.arguments, self.signature)
+
+
+class DumpAnd(DumpBySignature):
+
+    adapts(AndSig)
+
+    def __call__(self):
+        self.state.format("({ops:join{ AND }})", self.arguments)
+
+
+class DumpOr(DumpBySignature):
+
+    adapts(OrSig)
+
+    def __call__(self):
+        self.state.format("({ops:join{ OR }})", self.arguments)
+
+
+class DumpNot(DumpBySignature):
+
+    adapts(NotSig)
+
+    def __call__(self):
+        self.state.format("(NOT {op})", self.arguments)
+
+
+class DumpIsNull(DumpBySignature):
+
+    adapts(IsNullSig)
+
+    def __call__(self):
+        self.state.format("({op} IS {polarity:polarity}NULL)",
+                          self.arguments, self.signature)
+
+
+class DumpIfNull(DumpBySignature):
+
+    adapts(IfNullSig)
+
+    def __call__(self):
+        self.state.format("COALESCE({lop}, {rop})", self.arguments)
+
+
+class DumpNullIf(DumpBySignature):
+
+    adapts(NullIfSig)
+
+    def __call__(self):
+        self.state.format("NULLIF({lop}, {rop})", self.arguments)
+
+
+class DumpCompare(DumpBySignature):
+
+    adapts(CompareSig)
+
+    def __call__(self):
+        self.state.format("({lop} {relation:asis} {rop})",
+                          self.arguments, self.signature)
+
+
 class DumpColumn(Dump):
 
     adapts(ColumnPhrase)

src/htsql/tr/encode.py

 from .binding import (Binding, RootBinding, QueryBinding, SegmentBinding,
                       TableBinding, FreeTableBinding, AttachedTableBinding,
                       ColumnBinding, LiteralBinding, SieveBinding,
-                      SortBinding, EqualityBinding, TotalEqualityBinding,
-                      ConjunctionBinding, DisjunctionBinding,
-                      NegationBinding, CastBinding, WrapperBinding,
+                      SortBinding, CastBinding, WrapperBinding,
                       DirectionBinding, FormulaBinding)
 from .code import (ScalarSpace, DirectProductSpace, FiberProductSpace,
                    FilteredSpace, OrderedSpace,
-                   QueryExpr, SegmentExpr, LiteralCode,
-                   EqualityCode, TotalEqualityCode, FormulaCode,
-                   ConjunctionCode, DisjunctionCode, NegationCode,
+                   QueryExpr, SegmentExpr, LiteralCode, FormulaCode,
                    CastCode, ColumnUnit, ScalarUnit)
-from .signature import Signature
+from .signature import Signature, IsNullSig
 
 
 class EncodingState(object):
                            self.binding)
 
 
-class EncodeEquality(Encode):
-    """
-    Encodes an equality (``=``) binding.
-
-    Returns a :class:`htsql.tr.code.EqualityCode` node.
-    """
-
-    # FIXME: this and the next few encoders share the same structure:
-    # encode the operands and generate a new code node that have the
-    # same shape as the original binding node.  There must be a generic
-    # way to do it without adding an adapter for each binding.  This is
-    # also a problem with encoding functions.
-
-    adapts(EqualityBinding)
-
-    def __call__(self):
-        # Translate the operands and generate a code node.
-        lop = self.state.encode(self.binding.lop)
-        rop = self.state.encode(self.binding.rop)
-        return EqualityCode(lop, rop, self.binding)
-
-
-class EncodeTotalEquality(Encode):
-    """
-    Encodes a total equality (``==``) binding.
-
-    Returns a :class:`htsql.tr.code.TotalEqualityCode` node.
-    """
-
-    adapts(TotalEqualityBinding)
-
-    def __call__(self):
-        # Translate the operands and generate a code node.
-        lop = self.state.encode(self.binding.lop)
-        rop = self.state.encode(self.binding.rop)
-        return TotalEqualityCode(lop, rop, self.binding)
-
-
-class EncodeConjunction(Encode):
-    """
-    Encodes a logical "AND" (``&``) binding.
-
-    Returns a :class:`htsql.tr.code.ConjunctionCode` node.
-    """
-
-    adapts(ConjunctionBinding)
-
-    def __call__(self):
-        # Translate the operands and generate a code node.
-        ops = [self.state.encode(op) for op in self.binding.ops]
-        return ConjunctionCode(ops, self.binding)
-
-
-class EncodeDisjunction(Encode):
-    """
-    Encodes a logical "OR" (``|``) binding.
-
-    Returns a :class:`htsql.tr.code.DisjunctionCode` node.
-    """
-
-    adapts(DisjunctionBinding)
-
-    def __call__(self):
-        # Translate the operands and generate a code node.
-        ops = [self.state.encode(op) for op in self.binding.ops]
-        return DisjunctionCode(ops, self.binding)
-
-
-class EncodeNegation(Encode):
-    """
-    Encodes a logical "NOT" (``!``) binding.
-
-    Returns a :class:`htsql.tr.code.NegationCode` node.
-    """
-
-    adapts(NegationBinding)
-
-    def __call__(self):
-        # Translate the operand and generate a code node.
-        op = self.state.encode(self.binding.op)
-        return NegationCode(op, self.binding)
-
-
 class Convert(Adapter):
     """
     Encodes a cast binding to a code node.
         # represents some space.  In this case, Boolean cast produces
         # an expression which is `FALSE` when the space is empty and
         # `TRUE` otherwise.  The actual expression is:
-        #   `!(unit==null())`,
+        #   `!is_null(unit)`,
         # where `unit` is some non-nullable function on the space.
 
         # Translate the operand to a space node.
         space = self.state.relate(self.base)
-        # `TRUE` and `NULL` literals.
+        # A `TRUE` literal.
         true_literal = LiteralCode(True, coerce(BooleanDomain()), self.binding)
-        null_literal = LiteralCode(None, coerce(BooleanDomain()), self.binding)
         # A `TRUE` constant as a function on the space.
         unit = ScalarUnit(true_literal, space, self.binding)
-        # Return `!(unit==null())`.
-        return NegationCode(TotalEqualityCode(unit, null_literal, self.binding),
-                            self.binding)
+        # Return `!is_null(unit)`.
+        return FormulaCode(IsNullSig(-1), coerce(BooleanDomain()),
+                           self.binding, op=unit)
 
 
 class EncodeCast(Encode):

src/htsql/tr/fn/assemble.py

 """
 
 
-from ...adapter import adapts
+from ...adapter import adapts, adapts_none
 from ..assemble import EvaluateBySignature
-from .signature import ConcatenateSig, WrapExistsSig
+from ..frame import FormulaPhrase
+from .signature import ConcatenateSig, WrapExistsSig, TakeCountSig
 
 
-class EvaluateWrapExists(EvaluateBySignature):
+class EvaluateFunction(EvaluateBySignature):
+
+    adapts_none()
+
+    is_null_regular = True
+    is_nullable = True
+
+    def __call__(self):
+        arguments = self.arguments.map(self.state.evaluate)
+        if self.is_null_regular:
+            is_nullable = any(cell.is_nullable for cell in arguments.cells())
+        else:
+            is_nullable = self.is_nullable
+        return FormulaPhrase(self.signature,
+                             self.domain,
+                             is_nullable,
+                             self.code,
+                             **arguments)
+
+
+class EvaluateWrapExists(EvaluateFunction):
 
     adapts(WrapExistsSig)
     is_null_regular = False
     is_nullable = False
 
 
-class EvaluateConcatenate(EvaluateBySignature):
+class EvaluateTakeCount(EvaluateFunction):
+
+    adapts(TakeCountSig)
+    is_null_regular = False
+    is_nullable = False
+
+
+class EvaluateConcatenate(EvaluateFunction):
 
     adapts(ConcatenateSig)
     is_null_regular = False

src/htsql/tr/fn/bind.py

                        DateDomain, EnumDomain)
 from ..syntax import NumberSyntax, StringSyntax, IdentifierSyntax
 from ..binding import (LiteralBinding, SortBinding, SieveBinding,
-                       FormulaBinding, EqualityBinding, TotalEqualityBinding,
-                       ConjunctionBinding, DisjunctionBinding, NegationBinding,
-                       CastBinding, WrapperBinding, TitleBinding,
-                       DirectionBinding, Binding)
+                       FormulaBinding, CastBinding, WrapperBinding,
+                       TitleBinding, DirectionBinding, Binding)
 from ..bind import BindByName, BindingState
 from ..error import BindError
 from ..coerce import coerce
 from ..lookup import lookup
 from .signature import (Signature, ThisSig, RootSig, DirectSig, FiberSig, AsSig,
                         SortDirectionSig, LimitSig, SortSig, NullSig, TrueSig,
-                        FalseSig, CastSig, DateSig, EqualSig, AmongSig,
-                        TotallyEqualSig, AndSig, OrSig, NotSig, CompareSig,
+                        FalseSig, CastSig, DateSig, IsEqualSig, IsInSig,
+                        IsTotallyEqualSig, AndSig, OrSig, NotSig, CompareSig,
                         AddSig, ConcatenateSig, DateIncrementSig,
                         SubtractSig, DateDecrementSig, DateDifferenceSig,
                         MultiplySig, DivideSig, IsNullSig, NullIfSig,
                         IfNullSig, IfSig, SwitchSig, KeepPolaritySig,
                         ReversePolaritySig, RoundSig, RoundToSig, LengthSig,
-                        ContainsSig, ExistsSig, EverySig,
+                        ContainsSig, ExistsSig, EverySig, BinarySig,
                         UnarySig, CountSig, MinSig, MaxSig, SumSig, AvgSig)
 import sys
 
                                        self.syntax.mark)
         parent = CastBinding(parent, domain, parent.syntax)
         child = CastBinding(child, domain, child.syntax)
-        condition = EqualityBinding(parent, child, self.syntax)
+        condition = FormulaBinding(IsEqualSig(+1), coerce(BooleanDomain()),
+                                   self.syntax, lop=parent, rop=child)
         yield SieveBinding(binding, condition, self.syntax)
 
 
 
 class BindAmongBase(BindFunction):
 
-    signature = AmongSig
+    signature = IsInSig
     polarity = None
 
     def correlate(self, lop, rops):
         lop = CastBinding(lop, domain, lop.syntax)
         rops = [CastBinding(rop, domain, rop.syntax) for rop in rops]
         if len(rops) == 1:
-            binding = EqualityBinding(lop, rops[0], self.syntax)
-            if self.polarity == -1:
-                binding = NegationBinding(binding, self.syntax)
-            return binding
+            return FormulaBinding(IsEqualSig(self.polarity),
+                                  coerce(BooleanDomain()),
+                                  self.syntax, lop=lop, rop=rops[0])
         else:
             return FormulaBinding(self.signature(self.polarity),
                                   coerce(BooleanDomain()),
 
 class BindTotallyEqualBase(BindFunction):
 
-    signature = TotallyEqualSig
+    signature = IsTotallyEqualSig
     polarity = None
 
     def correlate(self, lop, rop):
             raise BindError("incompatible arguments", self.syntax.mark)
         lop = CastBinding(lop, domain, lop.syntax)
         rop = CastBinding(rop, domain, rop.syntax)
-        binding = TotalEqualityBinding(lop, rop, self.syntax)
-        if self.polarity == -1:
-            binding = NegationBinding(binding, self.syntax)
-        return binding
+        return FormulaBinding(IsTotallyEqualSig(self.polarity),
+                              coerce(BooleanDomain()), self.syntax,
+                              lop=lop, rop=rop)
 
 
 class BindTotallyEqual(BindTotallyEqualBase):
 class BindAnd(BindFunction):
 
     named('&')
-    signature = AndSig
+    signature = BinarySig
 
     def correlate(self, lop, rop):
-        lop = CastBinding(lop, coerce(BooleanDomain()), lop.syntax)
-        rop = CastBinding(rop, coerce(BooleanDomain()), rop.syntax)
-        return ConjunctionBinding([lop, rop], self.syntax)
+        domain = coerce(BooleanDomain())
+        lop = CastBinding(lop, domain, lop.syntax)
+        rop = CastBinding(rop, domain, rop.syntax)
+        return FormulaBinding(AndSig(), domain, self.syntax, ops=[lop, rop])
 
 
 class BindOr(BindFunction):
 
     named('|')
-    signature = OrSig
+    signature = BinarySig
 
     def correlate(self, lop, rop):
-        lop = CastBinding(lop, coerce(BooleanDomain()), lop.syntax)
-        rop = CastBinding(rop, coerce(BooleanDomain()), rop.syntax)
-        return DisjunctionBinding([lop, rop], self.syntax)
+        domain = coerce(BooleanDomain())
+        lop = CastBinding(lop, domain, lop.syntax)
+        rop = CastBinding(rop, domain, rop.syntax)
+        return FormulaBinding(OrSig(), domain, self.syntax, ops=[lop, rop])
 
 
 class BindNot(BindFunction):
     signature = NotSig
 
     def correlate(self, op):
-        op = CastBinding(op, coerce(BooleanDomain()), op.syntax)
-        return NegationBinding(op, self.syntax)
+        domain = coerce(BooleanDomain())
+        op = CastBinding(op, domain, op.syntax)
+        return FormulaBinding(self.signature(), domain, self.syntax, op=op)
 
 
 class BindCompare(BindFunction):
 class BindIsNull(BindHomoFunction):
 
     named('is_null')
-    signature = IsNullSig()
+    signature = IsNullSig(+1)
     codomain = BooleanDomain()
 
 

src/htsql/tr/fn/dump.py

 from .signature import (AddSig, ConcatenateSig, DateIncrementSig,
                         SubtractSig, DateDecrementSig, DateDifferenceSig,
                         MultiplySig, DivideSig, IfSig, SwitchSig,
-                        AmongSig, CompareSig, ReversePolaritySig,
+                        ReversePolaritySig,
                         RoundSig, RoundToSig, LengthSig,
                         WrapExistsSig, TakeCountSig, TakeMinSig, TakeMaxSig,
                         TakeSumSig, TakeAvgSig)
 
     def __call__(self):
         if self.template is None:
+            print self.phrase, self.phrase.signature
             raise NotImplementedError()
         self.state.format(self.template, self.arguments, self.signature)
 
         self.state.format(" END)")
 
 
-class DumpAmong(DumpFunction):
-
-    adapts(AmongSig)
-    template = "({lop} {polarity:polarity}IN ({rops:join{, }}))"
-
-
-class DumpCompare(DumpFunction):
-
-    adapts(CompareSig)
-    template = "({lop} {relation:asis} {rop})"
-
-
 class DumpReversePolarity(DumpFunction):
 
     adapts(ReversePolaritySig)

src/htsql/tr/fn/encode.py

 from ..error import EncodeError
 from ..coerce import coerce
 from ..binding import LiteralBinding, CastBinding
-from ..code import (NegationCode, LiteralCode, ScalarUnit, CorrelatedUnit,
+from ..code import (LiteralCode, ScalarUnit, CorrelatedUnit,
                     AggregateUnit, FilteredSpace, FormulaCode)
-from .signature import (NullIfSig, IfNullSig, QuantifySig, WrapExistsSig,
-                        AggregateSig, CountSig,TakeCountSig,
+from .signature import (NotSig, NullIfSig, IfNullSig, QuantifySig,
+                        WrapExistsSig, AggregateSig, CountSig,TakeCountSig,
                         MinSig, TakeMinSig, MaxSig, TakeMaxSig,
                         SumSig, TakeSumSig, AvgSig, TakeAvgSig)
 
 
     def __call__(self):
         op = self.state.encode(self.binding.op)
-        if self.signature.polarity == -1:
-            op = NegationCode(op, self.binding)
+        if self.signature.polarity < 0:
+            op = FormulaCode(NotSig(), op.domain, op.binding, op=op)
         space = self.state.relate(self.binding.base)
         plural_units = [unit for unit in op.units
                              if not space.spans(unit.space)]
                                    self.binding)
         wrapper = FormulaCode(WrapExistsSig(), op.domain, self.binding,
                               op=aggregate)
-        if self.signature.polarity == -1:
-            wrapper = NegationCode(wrapper, self.binding)
+        if self.signature.polarity < 0:
+            wrapper = FormulaCode(NotSig(), wrapper.domain, wrapper.binding,
+                                  op=wrapper)
         wrapper = ScalarUnit(wrapper, space, self.binding)
         return wrapper
 
     def take(self, op):
         false = LiteralCode(False, op.domain, op.binding)
         op = FormulaCode(NullIfSig(), op.domain, op.binding,
-                         lop=op, rops=[false])
+                         lop=op, rop=false)
         return FormulaCode(TakeCountSig(), self.binding.domain, self.binding,
                            op=op)
 
         zero = CastBinding(zero, op.domain, op.syntax)
         zero = self.state.encode(zero)
         return FormulaCode(IfNullSig(), op.domain, op.binding,
-                           lop=op, rops=[zero])
+                           lop=op, rop=zero)
 
 
 class EncodeMin(EncodeAggregate):
         zero = CastBinding(zero, op.domain, op.syntax)
         zero = self.state.encode(zero)
         return FormulaCode(IfNullSig(), op.domain, op.binding,
-                           lop=op, rops=[zero])
+                           lop=op, rop=zero)
 
 
 class EncodeAvg(EncodeAggregate):

src/htsql/tr/fn/reduce.py

 
 from ...adapter import adapts
 from ..reduce import ReduceBySignature
-from ..frame import (IsNullPhrase, NullIfPhrase, IfNullPhrase,
-                     LiteralPhrase, FormulaPhrase)
-from .signature import (IsNullSig, NullIfSig, IfNullSig,
-                        KeepPolaritySig, ConcatenateSig)
-
-
-class ReduceIsNull(ReduceBySignature):
-
-    adapts(IsNullSig)
-
-    def __call__(self):
-        phrase = IsNullPhrase(self.phrase.op, self.phrase.expression)
-        return self.state.reduce(phrase)
-
-
-class ReduceNullIfSig(ReduceBySignature):
-
-    adapts(NullIfSig)
-
-    def __call__(self):
-        phrase = self.phrase.lop
-        for rop in self.phrase.rops:
-            phrase = NullIfPhrase(phrase, rop, self.phrase.domain,
-                                  self.phrase.expression)
-        return self.state.reduce(phrase)
-
-
-class ReduceIfNullSig(ReduceBySignature):
-
-    adapts(IfNullSig)
-
-    def __call__(self):
-        phrase = self.phrase.lop
-        for rop in self.phrase.rops:
-            phrase = IfNullPhrase(phrase, rop, self.phrase.domain,
-                                  self.phrase.expression)
-        return self.state.reduce(phrase)
+from ..frame import LiteralPhrase, FormulaPhrase
+from .signature import KeepPolaritySig, ConcatenateSig, IfNullSig
 
 
 class ReduceKeepPolaritySig(ReduceBySignature):
         empty = LiteralPhrase('', self.phrase.domain, self.phrase.expression)
         lop = self.phrase.lop
         if lop.is_nullable:
-            lop = IfNullPhrase(lop, empty, lop.domain, lop.expression)
+            lop = FormulaPhrase(IfNullSig(), lop.domain, False,
+                                lop.expression, lop=lop, rop=empty)
         rop = self.phrase.rop
         if rop.is_nullable:
-            rop = IfNullPhrase(rop, empty, rop.domain, rop.expression)
+            rop = FormulaPhrase(IfNullSig(), rop.domain, False,
+                                rop.expression, lop=rop, rop=empty)
         return FormulaPhrase(self.phrase.signature, self.phrase.domain,
                              False, self.phrase.expression,
                              lop=self.state.reduce(lop),

src/htsql/tr/fn/signature.py

 
 
 from ..signature import (Signature, Slot, NullarySig, UnarySig, BinarySig,
-                         NArySig, ConnectiveSig, PolarSig, CompareSig)
+                         NArySig, ConnectiveSig, PolarSig, CompareSig,
+                         IsEqualSig, IsTotallyEqualSig, IsInSig, IsNullSig,
+                         IfNullSig, NullIfSig, AndSig, OrSig, NotSig)
 
 
 class ThisSig(Signature):
     ]
 
 
-class EqualSig(BinarySig, PolarSig):
-    pass
-
-
-class AmongSig(NArySig, PolarSig):
-    pass
-
-
-class TotallyEqualSig(BinarySig, PolarSig):
-    pass
-
-
-class AndSig(BinarySig):
-    pass
-
-
-class OrSig(BinarySig):
-    pass
-
-
-class NotSig(UnarySig):
-    pass
-
-
 class AddSig(BinarySig):
     pass
 
     ]
 
 
-class IsNullSig(UnarySig):
-    pass
-
-
-class NullIfSig(NArySig):
-    pass
-
-
-class IfNullSig(NArySig):
-    pass
-
-
 class IfSig(Signature):
 
     slots = [

src/htsql/tr/frame.py

 from .coerce import coerce
 from .code import Expression
 from .term import Term, QueryTerm
-from .signature import Signature, Bag
+from .signature import Signature, Bag, Formula
 
 
 class Clause(Comparable, Clonable, Printable):
         super(FalsePhrase, self).__init__(False, domain, expression)
 
 
-class EqualityPhraseBase(Phrase):
-    """
-    Represents an equality operator.
-
-    This is an abstract class for ``=``, ``!=``, ``==``, ``!==`` operators.
-
-    Class attributes:
-
-    `is_regular` (Boolean)
-        Indicates that the operator is regular: if any of the arguments is
-        ``NULL``, the value is also ``NULL``.
-
-    `is_total` (Boolean)
-        Indicates that the operator is total: no exception for ``NULL`` values.
-
-    `is_positive` (Boolean)
-        Indicates that the phrase represents an equality operator.
-
-    `is_negative` (Boolean)
-        Indicates that the phrase represents an inequality operator.
-
-    Constructor arguments:
-
-    `lop` (:class:`Phrase`)
-        The left operand.
-
-    `rop` (:class:`Phrase`)
-        The right operand.
-    """
-
-    is_regular = False
-    is_total = False
-    is_positive = False
-    is_negative = False
-
-    def __init__(self, lop, rop, expression):
-        assert isinstance(lop, Phrase)
-        assert isinstance(rop, Phrase)
-        domain = coerce(BooleanDomain())
-        is_nullable = self.is_regular and (lop.is_nullable or rop.is_nullable)
-        equality_vector = (lop, rop)
-        super(EqualityPhraseBase, self).__init__(domain, is_nullable,
-                                                 expression, equality_vector)
-        self.lop = lop
-        self.rop = rop
-
-
-class EqualityPhrase(EqualityPhraseBase):
-    """
-    Represents the "equality" (``=``) operator.
-
-    The regular "equality" operator treats ``NULL`` as a special value:
-    if any of the arguments is ``NULL``, the result is also ``NULL``.
-    """
-
-    is_regular = True
-    is_positive = True
-
-
-class InequalityPhrase(EqualityPhraseBase):
-    """
-    Represents the "inequality" (``!=``) operator.
-
-    The regular "inequality" operator treats ``NULL`` as a special value:
-    if any of the arguments is ``NULL``, the result is also ``NULL``.
-    """
-
-    is_regular = True
-    is_negative = True
-
-
-class TotalEqualityPhrase(EqualityPhraseBase):
-    """
-    Represents the "total equality" (``==``) operator.
-
-    The "total equality" operator treats ``NULL`` as a regular value.
-    """
-
-    is_total = True
-    is_positive = True
-
-
-class TotalInequalityPhrase(EqualityPhraseBase):
-    """
-    Represents the "total inequality" (``!==``) operator.
-
-    The "total inequality" operator treats ``NULL`` as a regular value.
-    """
-
-    is_total = True
-    is_negative = True
-
-
-class ConnectivePhraseBase(Phrase):
-    """
-    Represents an N-ary logical connective.
-
-    This is an abstract class for ``AND`` and ``OR`` operators.
-
-    `ops` (a list of :class:`Phrase`)
-        The operands.
-
-    Class attributes:
-
-    `is_contjunction` (Boolean)
-        Indicates that the phrase represents the ``AND`` operator.
-
-    `is_disjunction` (Boolean)
-        Indicates that the phrase represents the ``OR`` operator.
-    """
-
-    is_conjunction = False
-    is_disjunction = False
-
-    def __init__(self, ops, expression):
-        assert isinstance(ops, listof(Phrase))
-        assert all(isinstance(op.domain, BooleanDomain) for op in ops)
-        domain = coerce(BooleanDomain())
-        is_nullable = any(op.is_nullable for op in ops)
-        equality_vector = tuple(ops)
-        super(ConnectivePhraseBase, self).__init__(domain, is_nullable,
-                                                   expression, equality_vector)
-        self.ops = ops
-
-
-class ConjunctionPhrase(ConnectivePhraseBase):
-    """
-    Represents the logical ``AND`` operator.
-    """
-
-    is_conjunction = True
-
-
-class DisjunctionPhrase(ConnectivePhraseBase):
-    """
-    Represents the logical ``OR`` operator.
-    """
-
-    is_disjunction = True
-
-
-class NegationPhrase(Phrase):
-    """
-    Represents the logical ``NOT`` operator.
-
-    `op` (:class:`Phrase`)
-        The operand.
-    """
-
-    def __init__(self, op, expression):
-        assert isinstance(op, Phrase)
-        assert isinstance(op.domain, BooleanDomain)
-        domain = coerce(BooleanDomain())
-        is_nullable = op.is_nullable
-        equality_vector = (op,)
-        super(NegationPhrase, self).__init__(domain, is_nullable, expression,
-                                             equality_vector)
-        self.op = op
-
-
-class IsNullPhraseBase(Phrase):
-    """
-    Represents the ``IS NULL`` and ``IS NOT NULL`` operators.
-
-    `op` (:class:`Phrase`)
-        The operand.
-
-    Class attributes:
-
-    `is_positive` (Boolean)
-        Indicates that the phrase represents the ``IS NULL`` operator.
-
-    `is_negative` (Boolean)
-        Indicates that the phrase represents the ``IS NOT NULL`` operator.
-    """
-
-    is_positive = False
-    is_negative = False
-
-    def __init__(self, op, expression):
-        assert isinstance(op, Phrase)
-        domain = coerce(BooleanDomain())
-        is_nullable = False
-        equality_vector = (op,)
-        super(IsNullPhraseBase, self).__init__(domain, is_nullable, expression,
-                                               equality_vector)
-        self.op = op
-
-
-class IsNullPhrase(IsNullPhraseBase):
-    """
-    Represents the ``IS NULL`` operator.
-
-    The ``IS NULL`` operator produces ``TRUE`` when its operand is ``NULL``,
-    ``FALSE`` otherwise.
-    """
-
-    is_positive = True
-
-
-class IsNotNullPhrase(IsNullPhraseBase):
-    """
-    Represents the ``IS NOT NULL`` operator.
-
-    The ``IS NOT NULL`` operator produces ``FALSE`` when its operand is
-    ``NULL``, ``TRUE`` otherwise.
-    """
-
-    is_negative = True
-
-
-class IfNullPhrase(Phrase):
-    """
-    Represents the ``IFNULL`` operator.
-
-    The ``IFNULL`` operator takes two operands, returns the first one
-    if it is not equal to ``NULL``, otherwise returns the second operand.
-
-    `lop` (:class:`Phrase`)
-        The first operand.
-
-    `rop` (:class:`Phrase`)
-        The second operand.
-    """
-
-    def __init__(self, lop, rop, domain, expression):
-        assert isinstance(lop, Phrase)
-        assert isinstance(rop, Phrase)
-        assert isinstance(domain, Domain)
-        # Note: the result could be `NULL` only if both arguments are nullable
-        # (as opposed to most regular functions).
-        is_nullable = (lop.is_nullable and rop.is_nullable)
-        equality_vector = (lop, rop)
-        super(IfNullPhrase, self).__init__(domain, is_nullable, expression,
-                                           equality_vector)
-        self.lop = lop
-        self.rop = rop
-
-
-class NullIfPhrase(Phrase):
-    """
-    Represents the ``NULLIF`` operator.
-
-    The ``NULLIF`` operator takes two operands, returns the first one
-    if it is not equal to the second operand, otherwise returns ``NULL``.
-
-    `lop` (:class:`Phrase`)
-        The first operand.
-
-    `rop` (:class:`Phrase`)
-        The second operand.
-    """
-
-    def __init__(self, lop, rop, domain, expression):
-        assert isinstance(lop, Phrase)
-        assert isinstance(rop, Phrase)
-        assert isinstance(domain, Domain)
-        # Note: the result could be `NULL` even when both arguments are not
-        # nullable.
-        is_nullable = True
-        equality_vector = (lop, rop)
-        super(NullIfPhrase, self).__init__(domain, is_nullable, expression,
-                                           equality_vector)
-        self.lop = lop
-        self.rop = rop
-
-
 class CastPhrase(Phrase):
     """
     Represents the ``CAST`` operator.
         self.base = base
 
 
-class FormulaPhrase(Phrase):
+class FormulaPhrase(Formula, Phrase):
     """
     Represents a function or an operator expression.
 
         arguments = Bag(**arguments)
         assert arguments.admits(Phrase, signature)
         equality_vector = (signature, domain, arguments.freeze())
-        super(FormulaPhrase, self).__init__(domain, is_nullable, expression,
+        super(FormulaPhrase, self).__init__(signature, arguments,
+                                            domain, is_nullable, expression,
                                             equality_vector)
-        self.signature = signature
-        self.arguments = arguments
-        # For convenience, we permit access to function arguments using
-        # object attributes.
-        arguments.impress(self)
 
 
 class ExportPhrase(Phrase):

src/htsql/tr/reduce.py

 from .frame import (Clause, Frame, ScalarFrame, TableFrame, BranchFrame,
                     NestedFrame, QueryFrame, Phrase, LiteralPhrase,
                     NullPhrase, TruePhrase, FalsePhrase,
-                    EqualityPhraseBase, EqualityPhrase, InequalityPhrase,
-                    TotalEqualityPhrase, TotalInequalityPhrase,
-                    ConnectivePhraseBase, ConjunctionPhrase, NegationPhrase,
-                    IsNullPhraseBase, IsNullPhrase, IsNotNullPhrase,
-                    IfNullPhrase, NullIfPhrase, CastPhrase, FormulaPhrase,
+                    CastPhrase, FormulaPhrase,
                     ExportPhrase, ReferencePhrase, Anchor, LeadingAnchor)
-from .signature import Signature
+from .signature import (Signature, isformula, IsEqualSig, IsTotallyEqualSig,
+                        IsInSig, IsNullSig, IfNullSig, NullIfSig, AndSig,
+                        OrSig, NotSig)
 
 
 class ReducingState(object):
             if where is None:
                 where = head.where
             else:
-                where = ConjunctionPhrase([where, head.where],
-                                          where.expression)
+                is_nullable = (where.is_nullable or head.where.is_nullable)
+                where = FormulaPhrase(AndSig(), coerce(BooleanDomain()),
+                                      is_nullable, where.expression,
+                                      ops=[where, head.where])
 
         # Merge the `ORDER BY` clauses.  There are several possibilities:
         # - the inner `ORDER BY` is empty;
         return self.phrase
 
 
-class ReduceEquality(Reduce):
-    """
-    Reduces a (regular or total) (in)equality operator.
-    """
-
-    adapts(EqualityPhraseBase)
-
-    def __call__(self):
-        # Start with reducing the operands.
-        lop = self.state.reduce(self.phrase.lop)
-        rop = self.state.reduce(self.phrase.rop)
-
-        # Check if both operands are `NULL`.
-        if isinstance(lop, NullPhrase) and isinstance(rop, NullPhrase):
-            # Reduce:
-            #   null()=null(), null()!=null() => null()
-            #   null()==null() => true, null()!==null() => false()
-            if self.phrase.is_total:
-                if self.phrase.is_positive:
-                    return TruePhrase(self.phrase.expression)
-                if self.phrase.is_negative:
-                    return FalsePhrase(self.phrase.expression)
-            else:
-                return NullPhrase(self.phrase.domain, self.phrase.expression)
-
-        # Now suppose one of the operands (`rop`) is `NULL`.
-        if isinstance(lop, NullPhrase):
-            lop, rop = rop, lop
-        if isinstance(rop, NullPhrase):
-            # We could always reduce:
-            #   lop==null() => is_null(lop)
-            #   lop!==null() => is_not_null(lop)
-            # In addition, if we know that `lop` is not nullable, we could have
-            # reduced:
-            #   lop==null() => false()
-            #   lop!==null() => true()
-            # However that completely removes `lop` from the clause tree, which,
-            # in some cases, may change the semantics of SQL (for instance, when
-            # `lop` is an aggregate expression).  Therefore, we only apply this
-            # optimization when `lop` is a literal.  Similarly, we could reduce:
-            #   lop=null(), lop!=null() => null(),
-            # but we could do it safely only when `lop` is a literal.
-            if self.phrase.is_total:
-                if isinstance(lop, LiteralPhrase):
-                    if self.phrase.is_positive:
-                        return FalsePhrase(self.phrase.expression)
-                    if self.phrase.is_negative:
-                        return TruePhrase(self.phrase.expression)
-                if self.phrase.is_positive:
-                    return IsNullPhrase(lop, self.phrase.expression)
-                if self.phrase.is_negative:
-                    return IsNotNullPhrase(rop, self.phrase.expression)
-            elif isinstance(lop, LiteralPhrase):
-                return NullPhrase(self.phrase.domain, self.phrase.expression)
-
-        # Check if both arguments are boolean literals.
-        if (isinstance(lop, (TruePhrase, FalsePhrase)) and
-            isinstance(rop, (TruePhrase, FalsePhrase))):
-            # Reduce:
-            #   true()=true(), true()==true() => true()
-            #   ...
-            # Note: we do not apply the same optimization for literals of
-            # arbitrary type because we cannot precisely replicate the
-            # database equality operator.
-            if lop.value == rop.value:
-                if self.phrase.is_positive:
-                    return TruePhrase(self.phrase.expression)
-                if self.phrase.is_negative:
-                    return FalsePhrase(self.phrase.expression)
-            else:
-                if self.phrase.is_positive:
-                    return FalsePhrase(self.phrase.expression)
-                if self.phrase.is_negative:
-                    return TruePhrase(self.phrase.expression)
-
-        # When both operands are not nullable, we could replace a total
-        # comparison operator with a regular one.
-        if self.phrase.is_total and not (lop.is_nullable or rop.is_nullable):
-            if self.phrase.is_positive:
-                return EqualityPhrase(lop, rop, self.phrase.expression)
-            if self.phrase.is_negative:
-                return InequalityPhrase(lop, rop, self.phrase.expression)
-
-        # FIXME: we also need to replace a total comparison operator
-        # with a regular one for databases that do not have a native
-        # total comparison operator.
-
-        # None of specific optimizations were applied, just return
-        # the same operator with reduced operands.
-        return self.phrase.clone(lop=lop, rop=rop)
-
-
-class ReduceConnective(Reduce):
-    """
-    Reduces "AND" (``&``) and "OR" (``|``) operators.
-    """
-
-    adapts(ConnectivePhraseBase)
-
-    def __call__(self):
-        # Start with reducing the operands.
-        ops = []
-        duplicates = set()
-        for op in self.phrase.ops:
-            # Reduce the operand.
-            op = self.state.reduce(op)
-            # Weed out duplicates.
-            if op in duplicates:
-                continue
-            # The `AND` operator: weed out `TRUE`.
-            if self.phrase.is_conjunction and isinstance(op, TruePhrase):
-                continue
-            # The `OR` operator: weed out `FALSE`.
-            if self.phrase.is_disjunction and isinstance(op, FalsePhrase):
-                continue
-            ops.append(op)
-            duplicates.add(op)
-
-        # Consider the `AND` operator.
-        if self.phrase.is_conjunction:
-            # Reduce:
-            #   "&"() => true()
-            if not ops:
-                return TruePhrase(self.phrase.expression)
-            # We could reduce:
-            #   "&"(...,false(),...) => false()
-            # but that means we discard the rest of the operands
-            # from the clause tree, which is unsafe.  So we only
-            # do that when all operands are literals.
-            if any(isinstance(op, FalsePhrase) for op in ops):
-                if all(isinstance(op, LiteralPhrase) for op in ops):
-                    return FalsePhrase(self.phrase.expression)
-
-        # Consider the `OR` operator.
-        if self.phrase.is_disjunction:
-            # Reduce:
-            #   "|"() => false()
-            if not ops:
-                return FalsePhrase(self.phrase.expression)
-            # Reduce (when it is safe):
-            #   "|"(...,true(),...) => true()
-            if any(isinstance(op, TruePhrase) for op in ops):
-                if all(isinstance(op, LiteralPhrase) for op in ops):
-                    return TruePhrase(self.phrase.expression)
-
-        # Reduce:
-        #   "&"(op), "|"(op) => op
-        if len(ops) == 1:
-            return ops[0]
-
-        # Return the same operator with reduced operands.
-        return self.phrase.clone(ops=ops)
-
-
-class ReduceNegation(Reduce):
-    """
-    Reduces a "NOT" (``!``) operator.
-    """
-
-    adapts(NegationPhrase)
-
-    def __call__(self):
-        # Start with reducing the operand.
-        op = self.state.reduce(self.phrase.op)
-
-        # Reduce:
-        #   !null() => null()
-        #   !true() => false()
-        #   !false() => true()
-        if isinstance(op, NullPhrase):
-            return NullPhrase(self.phrase.domain, self.phrase.expression)
-        if isinstance(op, TruePhrase):
-            return FalsePhrase(self.phrase.expression)
-        if isinstance(op, FalsePhrase):
-            return TruePhrase(self.phrase.expression)
-        # Reduce:
-        #   !(lop=rop) => lop!=rop
-        #   !(lop!=rop) => lop=rop
-        #   !(lop==rop) => lop!==rop
-        #   !(lop!==rop) => lop==rop
-        if isinstance(op, EqualityPhrase):
-            return InequalityPhrase(op.lop, op.rop, self.phrase.expression)
-        if isinstance(op, InequalityPhrase):
-            return EqualityPhrase(op.lop, op.rop, self.phrase.expression)
-        if isinstance(op, TotalEqualityPhrase):
-            return TotalInequalityPhrase(op.lop, op.rop,
-                                         self.phrase.expression)
-        if isinstance(op, TotalInequalityPhrase):
-            return TotalEqualityPhrase(op.lop, op.rop, self.phrase.expression)
-        # Reduce:
-        #   !is_null(op) => is_not_null(op)
-        #   !is_not_null(op) => is_null(op)
-        if isinstance(op, IsNullPhrase):
-            return IsNotNullPhrase(op.op, self.phrase.expression)
-        if isinstance(op, IsNotNullPhrase):
-            return IsNullPhrase(op.op, self.phrase.expression)
-
-        # Return the same operator with a reduced operand.
-        return self.phrase.clone(op=op)
-
-
-class ReduceIsNull(Reduce):
-    """
-    Reduces ``IS NULL`` and ``IS NOT NULL`` clauses.
-    """
-
-    adapts(IsNullPhraseBase)
-
-    def __call__(self):
-        # Start with reducing the operand.
-        op = self.state.reduce(self.phrase.op)
-
-        # Reduce:
-        #   is_null(null()) => true()
-        #   is_not_null(false()) => false()
-        if isinstance(op, NullPhrase):
-            if self.phrase.is_positive:
-                return TruePhrase(self.phrase.expression)
-            if self.phrase.is_negative:
-                return FalsePhrase(self.phrase.expression)
-        # If the operand is not nullable, we could reduce the operator
-        # to a `TRUE` or a `FALSE` clause.  However it is only safe
-        # to do for a literal operand.
-        if isinstance(op, LiteralPhrase):
-            if self.phrase.is_positive:
-                return FalsePhrase(self.phrase.expression)
-            if self.phrase.is_negative:
-                return TruePhrase(self.phrase.expression)
-
-        # Return the same operator with a reduced operand.
-        return self.phrase.clone(op=op)
-
-
-class ReduceIfNull(Reduce):
-    """
-    Reduces an ``IFNULL`` clause.
-    """
-
-    adapts(IfNullPhrase)
-
-    def __call__(self):
-        # Reduce the operands.
-        lop = self.state.reduce(self.phrase.lop)
-        rop = self.state.reduce(self.phrase.rop)
-
-        # If the first operand is not nullable, then the operation is no-op,
-        # and we could just return the first operand discarding the second
-        # one.  However discarding a clause is not safe in general, so we
-        # only do that when the second operand is a literal.
-        if not lop.is_nullable and isinstance(rop, LiteralPhrase):
-            return lop
-        # Reduce:
-        #   if_null(lop, null()) => lop
-        if isinstance(rop, NullPhrase):
-            return lop
-        # Reduce:
-        #   if_null(null(), rop) => rop
-        if isinstance(lop, NullPhrase):
-            return rop
-
-        # Return the same operator with reduced operands.
-        return self.phrase.clone(lop=lop, rop=rop)
-
-
-class ReduceNullIf(Reduce):
-    """
-    Reduces a ``NULLIF`` clause.
-    """
-
-    adapts(NullIfPhrase)
-
-    def __call__(self):
-        # Reduce the operands.
-        lop = self.state.reduce(self.phrase.lop)
-        rop = self.state.reduce(self.phrase.rop)
-        # Reduce (when it is safe):
-        #   null_if(null(), rop) => null()
-        if isinstance(lop, NullPhrase):
-            if isinstance(rop, LiteralPhrase):
-                return lop
-        # Reduce:
-        #   null_if(lop, null()) => lop
-        if isinstance(rop, NullPhrase):
-            return lop
-        # When both operands are literals, we could determine the result
-        # immediately.  We should be careful though since we cannot precisely
-        # mimic the equality operator of the database.
-        if isinstance(lop, LiteralPhrase) and isinstance(rop, LiteralPhrase):
-            # Assume that if the literals are equal in Python, they would
-            # be equal for the database too.  The reverse is not valid in
-            # general, but still valid for boolean literals.
-            if lop.value == rop.value:
-                return NullPhrase(self.phrase.domain, self.phrase.expression)
-            elif isinstance(self.phrase.domain, BooleanDomain):
-                return lop
-
-        # Return the same operator with reduced operands.
-        return self.phrase.clone(lop=lop, rop=rop)
-
-
 class ReduceCast(Reduce):
     """
     Reduces a ``CAST`` operator.
         #   boolean(base) => !is_null(base)
         # There could be different implementations for specific
         # origin domains.
-        phrase = IsNotNullPhrase(self.base, self.phrase.expression)
+        phrase = FormulaPhrase(IsNullSig(-1), self.domain, False,
+                               self.phrase.expression, op=self.base)
         # We still need to reduce the phrase.
         return self.state.reduce(phrase)
 
             else:
                 return TruePhrase(self.phrase.expression)
         # If the operand is nullable, then:
-        #   boolean(base) => is_not_null(null_if(base, ''))
+        #   boolean(base) => !is_null(null_if(base, ''))
         # Otherwise:
         #   boolean(base) => (base!='')
         empty = LiteralPhrase('', coerce(StringDomain()),
                               self.phrase.expression)
         if not self.base.is_nullable:
-            phrase = InequalityPhrase(self.base, empty,
-                                      self.phrase.expression)
+            phrase = FormulaPhrase(IsEqualSig(-1), self.domain,
+                                   False, self.phrase.expression,
+                                   lop=self.base, rop=empty)
         else:
-            phrase = NullIfPhrase(self.base, empty, self.base.domain,
-                                  self.phrase.expression)
-            phrase = IsNotNullPhrase(phrase, self.phrase.expression)
+            phrase = FormulaPhrase(NullIfSig(), self.base.domain,
+                                   True, self.phrase.expression,
+                                   lop=self.base, rop=empty)
+            phrase = FormulaPhrase(IsNullSig(-1), self.domain,
+                                   False, self.phrase.expression, op=phrase)
 
         # We still need to reduce the expression.
         return self.state.reduce(phrase)
         return self.state.reduce(self.base)
 
 
+class ReduceFormula(Reduce):
+
+    adapts(FormulaPhrase)
+
+    def __call__(self):
+        reduce = ReduceBySignature(self.phrase, self.state)
+        return reduce()
+
+
 class ReduceBySignature(Adapter):
 
     adapts(Signature)
                              **arguments)
 
 
-class ReduceFormula(Reduce):
+class ReduceIsEqual(ReduceBySignature):
+    """
+    Reduces an (in)equality operator.
+    """
 
-    adapts(FormulaPhrase)
+    adapts(IsEqualSig)
 
     def __call__(self):
-        reduce = ReduceBySignature(self.phrase, self.state)
-        return reduce()
+        # Start with reducing the operands.
+        lop = self.state.reduce(self.phrase.lop)
+        rop = self.state.reduce(self.phrase.rop)
+
+        # Check if both operands are `NULL`.
+        if isinstance(lop, NullPhrase) and isinstance(rop, NullPhrase):
+            # Reduce:
+            #   null()=null(), null()!=null() => null()
+            return NullPhrase(self.phrase.domain, self.phrase.expression)
+
+        # Now suppose one of the operands (`rop`) is `NULL`.
+        if isinstance(lop, NullPhrase):
+            lop, rop = rop, lop
+        if isinstance(rop, NullPhrase):
+            # We could reduce:
+            #   lop=null(), lop!=null() => null(),
+            # but that completely removes `lop` from the clause tree, and thus
+            # may change the semantics of SQL (for instance, when `lop` is an
+            # aggregate expression).  So we only do that when `lop` is a
+            # literal.
+            if isinstance(lop, LiteralPhrase):
+                return NullPhrase(self.phrase.domain, self.phrase.expression)
+
+        # Check if both arguments are boolean literals.
+        if (isinstance(lop, (TruePhrase, FalsePhrase)) and
+            isinstance(rop, (TruePhrase, FalsePhrase))):
+            # Reduce:
+            #   true()=true() => true()
+            #   ...
+            # Note: we do not apply the same optimization for literals of
+            # arbitrary type because we cannot precisely replicate the
+            # database equality operator.
+            polarity = self.signature.polarity
+            if lop.value != rop.value:
+                polarity = -polarity
+            if polarity > 0:
+                return TruePhrase(self.phrase.expression)
+            else:
+                return FalsePhrase(self.phrase.expression)
+
+        # None of specific optimizations were applied, just return
+        # the same operator with reduced operands.
+        return self.phrase.clone(lop=lop, rop=rop)
+
+
+class ReduceIsTotallyEqual(ReduceBySignature):
+    """
+    Reduces a total (in)equality operator.
+    """
+
+    adapts(IsTotallyEqualSig)
+
+    def __call__(self):
+        # The polarity of the operator: +1 for `==`, -1 for `!==`.
+        polarity = self.signature.polarity
+
+        # Start with reducing the operands.
+        lop = self.state.reduce(self.phrase.lop)
+        rop = self.state.reduce(self.phrase.rop)
+
+        # Check if both operands are `NULL`.
+        if isinstance(lop, NullPhrase) and isinstance(rop, NullPhrase):
+            # Reduce:
+            #   null()==null() => true, null()!==null() => false()
+            if polarity > 0:
+                return TruePhrase(self.phrase.expression)
+            else:
+                return FalsePhrase(self.phrase.expression)
+
+        # Now suppose one of the operands (`rop`) is `NULL`.
+        if isinstance(lop, NullPhrase):
+            lop, rop = rop, lop
+        if isinstance(rop, NullPhrase):
+            # We could always reduce:
+            #   lop==null() => is_null(lop)
+            #   lop!==null() => !is_null(lop)
+            # In addition, if we know that `lop` is not nullable, we could have
+            # reduced:
+            #   lop==null() => false()
+            #   lop!==null() => true()
+            # However that completely removes `lop` from the clause tree, which,
+            # in some cases, may change the semantics of SQL (for instance, when
+            # `lop` is an aggregate expression).  Therefore, we only apply this
+            # optimization when `lop` is a literal.
+            if isinstance(lop, LiteralPhrase):
+                if polarity > 0:
+                    return FalsePhrase(self.phrase.expression)
+                else:
+                    return TruePhrase(self.phrase.expression)
+            return FormulaPhrase(IsNullSig(polarity), self.domain,
+                                 False, self.phrase.expression, op=lop)
+
+        # Check if both arguments are boolean literals.
+        if (isinstance(lop, (TruePhrase, FalsePhrase)) and
+            isinstance(rop, (TruePhrase, FalsePhrase))):
+            # Reduce:
+            #   true()==true() => true()
+            #   ...
+            # Note: we do not apply the same optimization for literals of
+            # arbitrary type because we cannot precisely replicate the
+            # database equality operator.
+            if polarity * (-1 if (lop.value != rop.value) else +1):