Commits

Kirill Simonov committed 8ec1274 Merge

Refactoring functions and the serializer. Still very much work-in-progress.

Replaced the old pull serializer with a new push-based one; that allows us
to nicely indent the generated SQL.

Added a `Signature` class for declaring function signatures. Reimplemented
function translators as adapters on the function signatures.

Comments (0)

Files changed (34)

src/htsql/adapter.py

         # (2) The signature of the component is more specific than
         #     the signature of the other component.
         # Note: In case if the component has more than one signature,
-        # we require that each of the signatures is equal or more specific
-        # than some signature of the other component and at least one
-        # signature is strictly more specific than some signature
-        # of the other component.
-        is_greater = True
-        is_strict = False
+        # we require that at least one of the signatures is more
+        # specific than some signature of the other component.  This
+        # rule does not guarantee anti-symmetricity, so ambiguously
+        # defined implementations may make the ordering ill defined.
+        # Validness of the ordering is verified in `Component.realize()`.
         for self_signature in component.signatures:
             for other_signature in other.signatures:
                 if aresubclasses(self_signature, other_signature):
                     if self_signature != other_signature:
-                        is_strict = True
-                    break
-            else:
-                is_greater = False
-        if is_greater and is_strict:
-            return True
+                        return True
 
         return False
 

src/htsql/fmt/text.py

         yield "\n"
         yield " ----\n"
         yield " %s\n" % request_title
-        yield " %s\n" % product.profile.plan.sql
+        for line in product.profile.plan.sql.splitlines():
+            yield " %s\n" % line
 
 
 class TextFormatter(Formatter):

src/htsql/request.py

 from .tr.compile import compile
 from .tr.assemble import assemble
 from .tr.reduce import reduce
-#from .tr.serialize import serialize
-from .tr.serializer import Serializer
+from .tr.serialize import serialize
 from .fmt.format import FindRenderer
 import urllib
 
         term = compile(expression)
         frame = assemble(term)
         frame = reduce(frame)
-        #plan = serialize(frame)
-        serializer = Serializer()
-        plan = serializer.serialize(frame)
+        plan = serialize(frame)
         return plan
 
     def produce(self):

src/htsql/tr/__init__.py

 """
 
 
+from . import fn
+
+

src/htsql/tr/assemble.py

 from .coerce import coerce
 from .code import (Code, LiteralCode, EqualityCode, TotalEqualityCode,
                    ConjunctionCode, DisjunctionCode, NegationCode,
-                   CastCode, Unit, ColumnUnit)
+                   FunctionCode, CastCode, Unit, ColumnUnit)
 from .term import (PreTerm, Term, UnaryTerm, BinaryTerm, TableTerm,
                    ScalarTerm, FilterTerm, JoinTerm, CorrelationTerm,
                    EmbeddingTerm, ProjectionTerm, OrderTerm, SegmentTerm,
                     EqualityPhrase, TotalEqualityPhrase, CastPhrase,
                     ConjunctionPhrase, DisjunctionPhrase, NegationPhrase,
                     ColumnPhrase, ReferencePhrase, EmbeddingPhrase,
-                    Anchor)
+                    FunctionPhrase, Anchor, LeadingAnchor)
+from .signature import Signature
 
 
 class Claim(Comparable, Printable):
         self.state.pop_gate()
         # Generate a `JOIN` clause.  Since it is the first (and the only)
         # subframe, the `JOIN` clause has no join condition.
-        anchor = Anchor(frame, None, False, False)
+        anchor = LeadingAnchor(frame)
         # Return a `FROM` list with a single subframe.
         return [anchor]
 
         # Restore the original dispatch context.
         self.state.pop_gate()
         # Generate a `JOIN` clause for the first subframe.
-        lanchor = Anchor(lframe, None, False, False)
+        lanchor = LeadingAnchor(lframe)
         # Set up the dispatch context for the second child term.
         self.state.push_gate(is_nullable=self.term.is_left,
                              dispatcher=self.term.rkid)
         condition = None
         if equalities:
             condition = ConjunctionPhrase(equalities, self.term.expression)
+        elif self.term.is_left or self.term.is_right:
+            condition = TruePhrase(self.term.expression)
         # Generate a `JOIN` clause for the second subframe.
         ranchor = Anchor(rframe, condition,
                          self.term.is_left, self.term.is_right)
         # Restore the original dispatch context.
         self.state.pop_gate()
         # Generate a `JOIN` clause (without any join condition).
-        anchor = Anchor(frame, None, False, False)
+        anchor = LeadingAnchor(frame)
         # Return a `FROM` list with a single subframe.
         return [anchor]
 
         return CastPhrase(base, self.code.domain, base.is_nullable, self.code)
 
 
+class EvaluateBySignature(Adapter):
+
+    adapts(Signature)
+
+    is_null_regular = True
+    is_nullable = True
+
+    @classmethod
+    def dispatch(interface, code, *args, **kwds):
+        assert isinstance(code, FunctionCode)
+        return (type(code.signature),)
+
+    def __init__(self, code, state):
+        assert isinstance(code, FunctionCode)
+        assert isinstance(state, AssemblingState)
+        self.code = code
+        self.state = state
+        self.signature = code.signature
+        self.domain = code.domain
+        self.arguments = code.arguments
+        self.signature.extract(self, self.arguments)
+
+    def __call__(self):
+        arguments = self.signature.apply(self.state.evaluate, self.arguments)
+        if self.is_null_regular:
+            is_nullable = any(argument.is_nullable
+                              for argument in self.signature.iterate(arguments))
+        else:
+            is_nullable = self.is_nullable
+        return FunctionPhrase(self.signature,
+                              self.domain,
+                              is_nullable,
+                              self.code,
+                              **arguments)
+
+
+class EvaluateFunction(Evaluate):
+
+    adapts(FunctionCode)
+
+    def __call__(self):
+        evaluate = EvaluateBySignature(self.code, self.state)
+        return evaluate()
+
+
 class EvaluateUnit(Evaluate):
     """
     Evaluates a unit.

src/htsql/tr/bind.py

 """
 
 
-from ..adapter import Adapter, adapts
+from ..util import tupleof
+from ..adapter import Adapter, Protocol, adapts
 from ..context import context
 from ..domain import (BooleanDomain, IntegerDomain, DecimalDomain,
                       FloatDomain, UntypedDomain)
 from .error import BindError
 from .syntax import (Syntax, QuerySyntax, SegmentSyntax, SelectorSyntax,
-                     SieveSyntax, OperatorSyntax, FunctionOperatorSyntax,
-                     FunctionCallSyntax, GroupSyntax, SpecifierSyntax,
-                     IdentifierSyntax, WildcardSyntax, StringSyntax,
-                     NumberSyntax)
+                     SieveSyntax, CallSyntax, OperatorSyntax,
+                     FunctionOperatorSyntax, FunctionCallSyntax, GroupSyntax,
+                     SpecifierSyntax, IdentifierSyntax, WildcardSyntax,
+                     StringSyntax, NumberSyntax)
 from .binding import (Binding, RootBinding, QueryBinding, SegmentBinding,
                       LiteralBinding, SieveBinding, CastBinding,
                       WrapperBinding)
-from .lookup import lookup, itemize
+from .lookup import lookup, itemize, normalize
 from .coerce import coerce
-from .fn.function import call
 import decimal
 
 
             If set, the lookup context is set to `base` when
             binding the syntax node.
         """
-        return call(syntax, self, base)
+        if base is not None:
+            self.push_base(base)
+        bind = BindByName(syntax, self)
+        bindings = list(bind())
+        if base is not None:
+            self.pop_base()
+        return bindings
 
 
 class Bind(Adapter):
         return self.state.call(self.syntax, base)
 
 
+class BindByName(Protocol):
+
+    names = []
+
+    @classmethod
+    def dispatch(interface, syntax, *args, **kwds):
+        assert isinstance(syntax, CallSyntax)
+        return (syntax.name, len(syntax.arguments))
+
+    @classmethod
+    def matches(component, dispatch_key):
+        assert isinstance(dispatch_key, tupleof(str, int))
+        key_name, key_arity = dispatch_key
+        for name in component.names:
+            arity = None
+            if isinstance(name, tuple):
+                name, arity = name
+            if name == key_name:
+                if arity is None or arity == key_arity:
+                    return True
+        return False
+
+    @classmethod
+    def dominates(component, other):
+        if issubclass(component, other):
+            return True
+        for name in component.names:
+            arity = None
+            if isinstance(name, tuple):
+                name, arity = name
+            for other_name in other.names:
+                other_arity = None
+                if isinstance(other_name, tuple):
+                    other_name, other_arity = other_name
+                if normalize(name) == normalize(other_name):
+                    if arity is not None and other_arity is None:
+                        return True
+        return False
+
+    def __init__(self, syntax, state):
+        assert isinstance(syntax, CallSyntax)
+        assert isinstance(state, BindingState)
+        self.syntax = syntax
+        self.name = syntax.name
+        self.arguments = syntax.arguments
+        self.state = state
+
+    def __call__(self):
+        raise BindError("unknown function %s" % self.name, self.syntax.mark)
+
+
 class BindGroup(Bind):
     """
     Binds a :class:`htsql.tr.syntax.GroupSyntax` node.

src/htsql/tr/binding.py

 from ..domain import Domain, VoidDomain, BooleanDomain, TupleDomain
 from .syntax import Syntax
 from .coerce import coerce
+from .signature import Signature
 
 
 class Binding(Clonable, Printable):
     # FIXME: should logical operators (`EqualityBinding`, etc) inherit
     # from `FunctionBinding`?
 
-    def __init__(self, domain, syntax, **arguments):
+    def __init__(self, signature, domain, syntax, **arguments):
+        assert isinstance(signature, Signature)
+        signature.verify(Binding, arguments)
         super(FunctionBinding, self).__init__(domain, syntax)
+        self.signature = signature
         self.arguments = arguments
-        for key in arguments:
-            setattr(self, key, arguments[key])
+        signature.extract(self, arguments)
 
 
 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
 
 
 class Expression(Comparable, Clonable, Printable):
         objects.
     """
 
-    def __init__(self, domain, binding, **arguments):
-        # Extract the units and the equality vector from the arguments.
-        # FIXME: messy, add some formal definition of the argument structure
-        # and/or a walker operator?
+    def __init__(self, signature, domain, binding, **arguments):
+        assert isinstance(signature, Signature)
+        signature.verify(Code, arguments)
         units = []
-        equality_vector = [domain]
-        for key in sorted(arguments):
-            value = arguments[key]
-            # Argument values are expected to be `Code` objects,
-            # list of `Code` objects or some other (immutable) objects.
-            if isinstance(value, Code):
-                units.extend(value.units)
-            if isinstance(value, list):
-                for item in value:
-                    if isinstance(item, Code):
-                        units.extend(item.units)
-                value = tuple(value)
-            equality_vector.append((key, value))
-        equality_vector = tuple(equality_vector)
-
+        for code in signature.iterate(arguments):
+            units.extend(code.units)
+        equality_vector = ((signature.__class__, domain)
+                           + signature.freeze(arguments))
         super(FunctionCode, self).__init__(
                     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.
-        for key in arguments:
-            setattr(self, key, arguments[key])
+        signature.extract(self, arguments)
 
 
 class Unit(Code):

src/htsql/tr/encode.py

                       SortBinding, EqualityBinding, TotalEqualityBinding,
                       ConjunctionBinding, DisjunctionBinding,
                       NegationBinding, CastBinding, WrapperBinding,
-                      DirectionBinding)
+                      DirectionBinding, FunctionBinding)
 from .code import (ScalarSpace, DirectProductSpace, FiberProductSpace,
                    FilteredSpace, OrderedSpace,
                    QueryExpr, SegmentExpr, LiteralCode,
-                   EqualityCode, TotalEqualityCode,
+                   EqualityCode, TotalEqualityCode, FunctionCode,
                    ConjunctionCode, DisjunctionCode, NegationCode,
                    CastCode, ColumnUnit, ScalarUnit)
+from .signature import Signature
 
 
 class EncodingState(object):
         return self.state.direct(self.binding.base)
 
 
+class EncodeBySignatureBase(Adapter):
+
+    adapts(Signature)
+
+    @classmethod
+    def dispatch(interface, binding, *args, **kwds):
+        assert isinstance(binding, FunctionBinding)
+        return (type(binding.signature),)
+
+    def __init__(self, binding, state):
+        assert isinstance(binding, FunctionBinding)
+        assert isinstance(state, EncodingState)
+        self.binding = binding
+        self.state = state
+        self.signature = binding.signature
+        self.domain = binding.domain
+        self.arguments = binding.arguments
+        self.signature.extract(self, self.arguments)
+
+
+class EncodeBySignature(EncodeBySignatureBase):
+
+    def __call__(self):
+        arguments = self.signature.apply(self.state.encode, self.arguments)
+        return FunctionCode(self.signature,
+                            self.domain,
+                            self.binding,
+                            **arguments)
+
+
+class RelateBySignature(EncodeBySignatureBase):
+
+    def __call__(self):
+        raise EncodeError("expected a valid space expression",
+                          self.binding.mark)
+
+
+class DirectBySignature(EncodeBySignatureBase):
+
+    def __call__(self):
+        return None
+
+
+class EncodeFunction(Encode):
+
+    adapts(FunctionBinding)
+
+    def __call__(self):
+        encode = EncodeBySignature(self.binding, self.state)
+        return encode()
+
+
+class RelateFunction(Relate):
+
+    adapts(FunctionBinding)
+
+    def __call__(self):
+        relate = RelateBySignature(self.binding, self.state)
+        return relate()
+
+
+class DirectFunction(Direct):
+
+    adapts(FunctionBinding)
+
+    def __call__(self):
+        direct = DirectBySignature(self.binding, self.state)
+        return direct()
+
+
 class EncodeWrapper(Encode):
     """
     Translates a wrapper binding to a code node.

src/htsql/tr/error.py

     kind = "compile error"
 
 
+class SerializeError(TranslateError):
+    """
+    Represents a serializer error.
+
+    This error is raized when the serializer is unable to translate a clause
+    node to SQL.
+    """
+
+    kind = "serialize error"
+
+

src/htsql/tr/fn/__init__.py

 """
 
 
+from . import bind
+from . import encode
+from . import assemble
+from . import reduce
+from . import serialize
+
+

src/htsql/tr/fn/assemble.py

+#
+# Copyright (c) 2006-2010, Prometheus Research, LLC
+# Authors: Clark C. Evans <cce@clarkevans.com>,
+#          Kirill Simonov <xi@resolvent.net>
+#
+
+
+"""
+:mod:`htsql.tr.fn.assemble`
+===========================
+"""
+
+
+from ...adapter import adapts
+from ..assemble import EvaluateBySignature
+from .signature import ConcatenateSig, WrapExistsSig
+
+
+class EvaluateWrapExists(EvaluateBySignature):
+
+    adapts(WrapExistsSig)
+    is_null_regular = False
+    is_nullable = False
+
+
+class EvaluateConcatenate(EvaluateBySignature):
+
+    adapts(ConcatenateSig)
+    is_null_regular = False
+    is_nullable = False
+
+

src/htsql/tr/fn/bind.py

+#
+# Copyright (c) 2006-2010, Prometheus Research, LLC
+# Authors: Clark C. Evans <cce@clarkevans.com>,
+#          Kirill Simonov <xi@resolvent.net>
+#
+
+
+"""
+:mod:`htsql.tr.fn.bind`
+=======================
+"""
+
+
+from ...adapter import Adapter, adapts, adapts_many, adapts_none, named
+from ...domain import (Domain, UntypedDomain, BooleanDomain, StringDomain,
+                       NumberDomain, IntegerDomain, DecimalDomain, FloatDomain,
+                       DateDomain, EnumDomain)
+from ..syntax import NumberSyntax, StringSyntax, IdentifierSyntax
+from ..binding import (LiteralBinding, SortBinding, SieveBinding,
+                       FunctionBinding, EqualityBinding, TotalEqualityBinding,
+                       ConjunctionBinding, DisjunctionBinding, NegationBinding,
+                       CastBinding, WrapperBinding, TitleBinding,
+                       DirectionBinding)
+from ..bind import BindByName
+from ..error import BindError
+from ..coerce import coerce
+from ..lookup import lookup
+from .signature import (ThisSig, RootSig, DirectSig, FiberSig, AsSig,
+                        SortDirectionSig, LimitSig, SortSig, NullSig, TrueSig,
+                        FalseSig, CastSig, DateSig, EqualSig, AmongSig,
+                        TotallyEqualSig, AndSig, OrSig, NotSig, CompareSig,
+                        AddSig, NumericAddSig, ConcatenateSig,
+                        DateIncrementSig, SubtractSig, NumericSubtractSig,
+                        DateDecrementSig, DateDifferenceSig, MultiplySig,
+                        NumericMultiplySig, DivideSig, NumericDivideSig,
+                        IsNullSig, NullIfSig, IfNullSig, IfSig, SwitchSig,
+                        KeepPolaritySig, ReversePolaritySig,
+                        NumericKeepPolaritySig, NumericReversePolaritySig,
+                        RoundSig, RoundToSig, LengthSig, StringLengthSig,
+                        ContainsSig, StringContainsSig, ExistsSig, EverySig,
+                        UnarySig, CountSig, MinSig, MaxSig, SumSig, AvgSig)
+
+
+class BindFunction(BindByName):
+
+    signature = None
+
+    def match(self):
+        operands = self.syntax.arguments[:]
+        arguments = {}
+        parameters = []
+        if self.signature is not None:
+            parameters = self.signature.parameters
+        for index, parameter in enumerate(parameters):
+            name = parameter.name
+            value = None
+            if not operands:
+                if parameter.is_mandatory:
+                    raise BindError("missing argument %s" % name,
+                                    self.syntax.mark)
+                if parameter.is_list:
+                    value = []
+            elif not parameter.is_list:
+                value = operands.pop(0)
+            else:
+                if index == len(self.signature.parameters)-1:
+                    value = operands[:]
+                    operands[:] = []
+                else:
+                    value = [operands.pop(0)]
+            arguments[name] = value
+        if operands:
+            raise BindError("unexpected argument", operands[0].mark)
+        return arguments
+
+    def bind(self):
+        arguments = self.match()
+        bound_arguments = {}
+        parameters = []
+        if self.signature is not None:
+            parameters = self.signature.parameters
+        for parameter in parameters:
+            name = parameter.name
+            value = arguments[name]
+            bound_value = None
+            if not parameter.is_list:
+                if value is not None:
+                    bound_values = self.state.bind_all(value)
+                    if len(bound_values) > 1:
+                        raise BindError("unexpected list argument",
+                                        value.mark)
+                    if parameter.is_mandatory and not bound_values:
+                        raise BindError("unexpected empty argument",
+                                        value.mark)
+                    if bound_values:
+                        [bound_value] = bound_values
+            else:
+                if len(value) > 1:
+                    bound_value = [self.state.bind(item) for item in value]
+                elif len(value) == 1:
+                    [value] = value
+                    bound_value = self.state.bind_all(value)
+                    if parameter.is_mandatory and not bound_value:
+                        raise BindError("missing argument %s" % name,
+                                        value.mark)
+                else:
+                    bound_value = []
+            bound_arguments[name] = bound_value
+        return bound_arguments
+
+    def correlate(self, **arguments):
+        raise NotImplementedError()
+
+    def __call__(self):
+        arguments = self.bind()
+        yield self.correlate(**arguments)
+
+
+class BindMacro(BindFunction):
+
+    def expand(self, **arguments):
+        raise NotImplementedError()
+
+    def __call__(self):
+        arguments = self.match()
+        return self.expand(**arguments)
+
+
+class BindRoot(BindMacro):
+
+    named('root')
+    signature = RootSig()
+
+    def expand(self):
+        yield WrapperBinding(self.state.root, self.syntax)
+
+
+class BindThis(BindMacro):
+
+    named('this')
+    signature = ThisSig()
+
+    def expand(self):
+        yield WrapperBinding(self.state.base, self.syntax)
+
+
+class BindDirect(BindMacro):
+
+    named('direct')
+    signature = DirectSig()
+
+    def expand(self, table):
+        if not isinstance(table, IdentifierSyntax):
+            raise BindError("an identifier expected", table.mark)
+        binding = lookup(self.state.root, table)
+        if binding is None:
+            raise InvalidArgumentError("unknown identifier", table.mark)
+        binding = binding.clone(base=self.state.base)
+        yield WrapperBinding(binding, self.syntax)
+
+
+class BindFiber(BindMacro):
+
+    named('fiber')
+    signature = FiberSig()
+
+    def expand(self, table, image, counterimage=None):
+        if not isinstance(table, IdentifierSyntax):
+            raise BindError("an identifier expected", table.mark)
+        binding = lookup(self.state.root, table)
+        if binding is None:
+            raise InvalidArgumentError("unknown identifier", table.mark)
+        binding = binding.clone(base=self.state.base)
+        parent = self.state.bind(image)
+        if counterimage is None:
+            counterimage = image
+        child = self.state.bind(counterimage, base=binding)
+        domain = coerce(parent.domain, child.domain)
+        if domain is None:
+            raise InvalidArgumentError("incompatible images",
+                                       self.syntax.mark)
+        parent = CastBinding(parent, domain, parent.syntax)
+        child = CastBinding(child, domain, child.syntax)
+        condition = EqualityBinding(parent, child, self.syntax)
+        yield SieveBinding(binding, condition, self.syntax)
+
+
+class BindAs(BindMacro):
+
+    named('as')
+    signature = AsSig()
+
+    def expand(self, base, title):
+        if not isinstance(title, (StringSyntax, IdentifierSyntax)):
+            raise BindError("expected a string literal or an identifier",
+                            title.mark)
+        base = self.state.bind(base)
+        yield TitleBinding(base, title.value, self.syntax)
+
+
+class BindDirectionBase(BindMacro):
+
+    def expand(self, base):
+        for base in self.state.bind_all(base):
+            yield DirectionBinding(base, self.signature.direction, self.syntax)
+
+
+class BindAscDir(BindDirectionBase):
+
+    named('_+')
+    signature = SortDirectionSig(+1)
+
+
+class BindDescDir(BindDirectionBase):
+
+    named('_-')
+    signature = SortDirectionSig(-1)
+
+
+class BindLimit(BindMacro):
+
+    named('limit')
+    signature = LimitSig()
+
+    def parse(self, argument):
+        try:
+            if not isinstance(argument, NumberSyntax):
+                raise ValueError
+            value = int(argument.value)
+            if not (value >= 0):
+                raise ValueError
+        except ValueError:
+            raise BindError("expected a non-negative integer", argument.mark)
+        return value
+
+    def expand(self, limit, offset=None):
+        limit = self.parse(limit)
+        if offset is not None:
+            offset = self.parse(offset)
+        yield SortBinding(self.state.base, [], limit, offset, self.syntax)
+
+
+class BindSort(BindMacro):
+
+    named('sort')
+    signature = SortSig()
+
+    def expand(self, order):
+        bindings = []
+        for item in order:
+            for binding in self.state.bind_all(item):
+                domain = coerce(binding.domain)
+                if domain is None:
+                    raise BindError("incompatible expression type",
+                                    binding.mark)
+                binding = CastBinding(binding, domain, binding.syntax)
+                bindings.append(binding)
+        yield SortBinding(self.state.base, bindings, None, None, self.syntax)
+
+
+class BindNull(BindMacro):
+
+    named('null')
+    signature = NullSig()
+
+    def expand(self):
+        yield LiteralBinding(None, UntypedDomain(), self.syntax)
+
+
+class BindTrue(BindMacro):
+
+    named('true')
+    signature = TrueSig()
+
+    def expand(self):
+        yield LiteralBinding(True, coerce(BooleanDomain()), self.syntax)
+
+
+class BindFalse(BindMacro):
+
+    named('false')
+    signature = FalseSig()
+
+    def expand(self):
+        yield LiteralBinding(False, coerce(BooleanDomain()), self.syntax)
+
+
+class BindCast(BindFunction):
+
+    signature = CastSig()
+    codomain = None
+
+    def correlate(self, base):
+        domain = coerce(self.codomain)
+        return CastBinding(base, domain, self.syntax)
+
+
+class BindBooleanCast(BindCast):
+
+    named('boolean')
+    codomain = BooleanDomain()
+
+
+class BindStringCast(BindCast):
+
+    named('string')
+    codomain = StringDomain()
+
+
+class BindIntegerCast(BindCast):
+
+    named('integer')
+    codomain = IntegerDomain()
+
+
+class BindDecimalCast(BindCast):
+
+    named('decimal')
+    codomain = DecimalDomain()
+
+
+class BindFloatCast(BindCast):
+
+    named('float')
+    codomain = FloatDomain()
+
+
+class BindDateCast(BindCast):
+
+    named('date')
+    codomain = DateDomain()
+
+
+class BindMonoFunction(BindFunction):
+
+    domains = []
+    codomain = None
+
+    def correlate(self, **arguments):
+        assert self.signature is not None
+        assert len(self.signature.parameters) == len(self.domains)
+        assert self.codomain is not None
+        cast_arguments = {}
+        for domain, parameter in zip(self.domains, self.signature.parameters):
+            domain = coerce(domain)
+            name = parameter.name
+            value = arguments[name]
+            if not parameter.is_list:
+                if value is not None:
+                    value = CastBinding(value, domain, value.syntax)
+            else:
+                value = [CastBinding(item, domain, item.syntax)
+                         for item in value]
+            cast_arguments[name] = value
+        return FunctionBinding(self.signature, coerce(self.codomain),
+                               self.syntax, **cast_arguments)
+
+
+class BindDate(BindMonoFunction):
+
+    named(('date', 3))
+    signature = DateSig()
+    domains = [IntegerDomain(), IntegerDomain(), IntegerDomain()]
+    codomain = DateDomain()
+
+
+class BindAmongBase(BindFunction):
+
+    def correlate(self, lop, rops):
+        domain = coerce(lop.domain, *(rop.domain for rop in rops))
+        if domain is None:
+            raise BindError("incompatible arguments", self.syntax.mark)
+        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.signature.polarity == -1:
+                binding = NegationBinding(binding, self.syntax)
+            return binding
+        else:
+            return FunctionBinding(self.signature, coerce(BooleanDomain()),
+                                   self.syntax, lop=lop, rops=rops)
+
+
+class BindAmong(BindAmongBase):
+
+    named('=')
+    signature = AmongSig(+1)
+
+
+class BindNotAmong(BindAmongBase):
+
+    named('!=')
+    signature = AmongSig(-1)
+
+
+class BindTotallyEqualBase(BindFunction):
+
+    def correlate(self, lop, rop):
+        domain = coerce(lop.domain, rop.domain)
+        if domain is None:
+            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.signature.polarity == -1:
+            binding = NegationBinding(binding, self.syntax)
+        return binding
+
+
+class BindTotallyEqual(BindTotallyEqualBase):
+
+    named('==')
+    signature = TotallyEqualSig(+1)
+
+
+class BindTotallyNotEqual(BindTotallyEqualBase):
+
+    named('!==')
+    signature = TotallyEqualSig(-1)
+
+
+class BindAnd(BindFunction):
+
+    named('&')
+    signature = AndSig()
+
+    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)
+
+
+class BindOr(BindFunction):
+
+    named('|')
+    signature = OrSig()
+
+    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)
+
+
+class BindNot(BindFunction):
+
+    named('!_')
+    signature = NotSig()
+
+    def correlate(self, op):
+        op = CastBinding(op, coerce(BooleanDomain()), op.syntax)
+        return NegationBinding(op, self.syntax)
+
+
+class BindCompare(BindFunction):
+
+    def correlate(self, lop, rop):
+        domain = coerce(lop.domain, rop.domain)
+        if domain is None:
+            raise BindError("incompatible arguments", self.syntax.mark)
+        lop = CastBinding(lop, domain, lop.syntax)
+        rop = CastBinding(rop, domain, rop.syntax)
+        comparable = Comparable(domain)
+        if not comparable():
+            raise BindError("uncomparable arguments", self.syntax.mark)
+        return FunctionBinding(self.signature, coerce(BooleanDomain()),
+                               self.syntax, lop=lop, rop=rop)
+
+
+class BindLessThan(BindCompare):
+
+    named('<')
+    signature = CompareSig('<')
+
+
+class BindLessThanOrEqual(BindCompare):
+
+    named('<=')
+    signature = CompareSig('<=')
+
+
+class BindGreaterThan(BindCompare):
+
+    named('>')
+    signature = CompareSig('>')
+
+
+class BindGreaterThanOrEqual(BindCompare):
+
+    named('>=')
+    signature = CompareSig('>=')
+
+
+class Comparable(Adapter):
+
+    adapts(Domain)
+
+    def __init__(self, domain):
+        self.domain = domain
+
+    def __call__(self):
+        return False
+
+
+class ComparableDomains(Comparable):
+
+    adapts_many(IntegerDomain, DecimalDomain, FloatDomain,
+                StringDomain, EnumDomain, DateDomain)
+
+    def __call__(self):
+        return True
+
+
+class Correlate(Adapter):
+
+    signature = None
+    domains = []
+    codomain = None
+
+    def __call__(self):
+        return (self.signature is not None)
+
+
+class BindPolyFunction(BindFunction):
+
+    correlation = None
+
+    def correlate(self, **arguments):
+        domains = []
+        for parameter in self.signature.parameters:
+            if parameter.is_list or not parameter.is_mandatory:
+                break
+            name = parameter.name
+            value = arguments[name]
+            domains.append(value.domain)
+        correlate = self.correlation(*domains)
+        if not correlate():
+            raise BindError("incompatible arguments", self.syntax.mark)
+        correlated_arguments = arguments.copy()
+        for domain, parameter in zip(correlate.domains,
+                                     self.signature.parameters):
+            name = parameter.name
+            value = correlated_arguments[name]
+            value = CastBinding(value, coerce(domain), value.syntax)
+            correlated_arguments[name] = value
+        return FunctionBinding(correlate.signature, coerce(correlate.codomain),
+                               self.syntax, **correlated_arguments)
+
+
+class CorrelateAdd(Correlate):
+
+    adapts(Domain, Domain)
+
+
+class BindAdd(BindPolyFunction):
+
+    named('+')
+    signature = AddSig()
+    correlation = CorrelateAdd
+
+
+class CorrelateIntegerAdd(CorrelateAdd):
+
+    adapts(IntegerDomain, IntegerDomain)
+    signature = NumericAddSig()
+    domains = [IntegerDomain(), IntegerDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateDecimalAdd(CorrelateAdd):
+
+    adapts_many((IntegerDomain, DecimalDomain),
+                (DecimalDomain, IntegerDomain),
+                (DecimalDomain, DecimalDomain))
+    signature = NumericAddSig()
+    domains = [DecimalDomain(), DecimalDomain()]
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatAdd(CorrelateAdd):
+
+    adapts_many((IntegerDomain, FloatDomain),
+                (DecimalDomain, FloatDomain),
+                (FloatDomain, IntegerDomain),
+                (FloatDomain, DecimalDomain),
+                (FloatDomain, FloatDomain))
+    signature = NumericAddSig()
+    domains = [FloatDomain(), FloatDomain()]
+    codomain = FloatDomain()
+
+
+class CorrelateDateIncrement(CorrelateAdd):
+
+    adapts(DateDomain, IntegerDomain)
+    signature = DateIncrementSig()
+    domains = [DateDomain(), IntegerDomain()]
+    codomain = DateDomain()
+
+
+class CorrelateConcatenate(CorrelateAdd):
+
+    adapts_many((UntypedDomain, UntypedDomain),
+                (UntypedDomain, StringDomain),
+                (StringDomain, UntypedDomain),
+                (StringDomain, StringDomain))
+    signature = ConcatenateSig()
+    domains = [StringDomain(), StringDomain()]
+    codomain = StringDomain()
+
+
+class CorrelateSubtract(Correlate):
+
+    adapts(Domain, Domain)
+
+
+class BindSubtract(BindPolyFunction):
+
+    named('-')
+    signature = SubtractSig()
+    correlation = CorrelateSubtract
+
+
+class CorrelateIntegerSubtract(CorrelateSubtract):
+
+    adapts(IntegerDomain, IntegerDomain)
+    signature = NumericSubtractSig()
+    domains = [IntegerDomain(), IntegerDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateDecimalSubtract(CorrelateSubtract):
+
+    adapts_many((IntegerDomain, DecimalDomain),
+                (DecimalDomain, IntegerDomain),
+                (DecimalDomain, DecimalDomain))
+    signature = NumericSubtractSig()
+    domains = [DecimalDomain(), DecimalDomain()]
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatSubtract(CorrelateSubtract):
+
+    adapts_many((IntegerDomain, FloatDomain),
+                (DecimalDomain, FloatDomain),
+                (FloatDomain, IntegerDomain),
+                (FloatDomain, DecimalDomain),
+                (FloatDomain, FloatDomain))
+    signature = NumericSubtractSig()
+    domains = [FloatDomain(), FloatDomain()]
+    codomain = FloatDomain()
+
+
+class CorrelateDateDecrement(CorrelateSubtract):
+
+    adapts(DateDomain, IntegerDomain)
+    signature = DateDecrementSig()
+    domains = [DateDomain(), IntegerDomain()]
+    codomain = DateDomain()
+
+
+class CorrelateDateDifference(CorrelateSubtract):
+
+    adapts(DateDomain, DateDomain)
+    signature = DateDifferenceSig()
+    domains = [DateDomain(), DateDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateMultiply(Correlate):
+
+    adapts(Domain, Domain)
+
+
+class BindMultiply(BindPolyFunction):
+
+    named('*')
+    signature = MultiplySig()
+    correlation = CorrelateMultiply
+
+
+class CorrelateIntegerMultiply(CorrelateMultiply):
+
+    adapts(IntegerDomain, IntegerDomain)
+    signature = NumericMultiplySig()
+    domains = [IntegerDomain(), IntegerDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateDecimalMultiply(CorrelateMultiply):
+
+    adapts_many((IntegerDomain, DecimalDomain),
+                (DecimalDomain, IntegerDomain),
+                (DecimalDomain, DecimalDomain))
+    signature = NumericMultiplySig()
+    domains = [DecimalDomain(), DecimalDomain()]
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatMultiply(CorrelateMultiply):
+
+    adapts_many((IntegerDomain, FloatDomain),
+                (DecimalDomain, FloatDomain),
+                (FloatDomain, IntegerDomain),
+                (FloatDomain, DecimalDomain),
+                (FloatDomain, FloatDomain))
+    signature = NumericMultiplySig()
+    domains = [FloatDomain(), FloatDomain()]
+    codomain = FloatDomain()
+
+
+class CorrelateDivide(Correlate):
+
+    adapts(Domain, Domain)
+
+
+class BindDivide(BindPolyFunction):
+
+    named('/')
+    signature = DivideSig()
+    correlation = CorrelateDivide
+
+
+class CorrelateDecimalDivide(CorrelateDivide):
+
+    adapts_many((IntegerDomain, IntegerDomain),
+                (IntegerDomain, DecimalDomain),
+                (DecimalDomain, IntegerDomain),
+                (DecimalDomain, DecimalDomain))
+    signature = NumericDivideSig()
+    domains = [DecimalDomain(), DecimalDomain()]
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatDivide(CorrelateDivide):
+
+    adapts_many((IntegerDomain, FloatDomain),
+                (DecimalDomain, FloatDomain),
+                (FloatDomain, IntegerDomain),
+                (FloatDomain, DecimalDomain),
+                (FloatDomain, FloatDomain))
+    signature = NumericDivideSig()
+    domains = [FloatDomain(), FloatDomain()]
+    codomain = FloatDomain()
+
+
+class CorrelateKeepPolarity(Correlate):
+
+    adapts(Domain)
+
+
+class BindKeepPolarity(BindPolyFunction):
+
+    named('+_')
+    signature = KeepPolaritySig()
+    correlation = CorrelateKeepPolarity
+
+
+class CorrelateIntegerKeepPolarity(CorrelateKeepPolarity):
+
+    adapts(IntegerDomain)
+    signature = NumericKeepPolaritySig()
+    domains = [IntegerDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateDecimalKeepPolarity(CorrelateKeepPolarity):
+
+    adapts(DecimalDomain)
+    signature = NumericKeepPolaritySig()
+    domains = [DecimalDomain()]
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatKeepPolarity(CorrelateKeepPolarity):
+
+    adapts(FloatDomain)
+    signature = NumericKeepPolaritySig()
+    domains = [FloatDomain()]
+    codomain = FloatDomain()
+
+
+class CorrelateReversePolarity(Correlate):
+
+    adapts(Domain)
+
+
+class BindReversePolarity(BindPolyFunction):
+
+    named('-_')
+    signature = ReversePolaritySig()
+    correlation = CorrelateReversePolarity
+
+
+class CorrelateIntegerReversePolarity(CorrelateReversePolarity):
+
+    adapts(IntegerDomain)
+    signature = NumericReversePolaritySig()
+    domains = [IntegerDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateDecimalReversePolarity(CorrelateReversePolarity):
+
+    adapts(DecimalDomain)
+    signature = NumericReversePolaritySig()
+    domains = [DecimalDomain()]
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatReversePolarity(CorrelateReversePolarity):
+
+    adapts(FloatDomain)
+    signature = NumericReversePolaritySig()
+    domains = [FloatDomain()]
+    codomain = FloatDomain()
+
+
+class CorrelateRound(Correlate):
+
+    adapts(Domain)
+
+
+class BindRound(BindPolyFunction):
+
+    named('round')
+    signature = RoundSig()
+    correlation = CorrelateRound
+
+
+class CorrelateDecimalRound(CorrelateRound):
+
+    adapts_many(IntegerDomain,
+                DecimalDomain)
+    signature = RoundSig()
+    domains = [DecimalDomain()]
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatRound(CorrelateRound):
+
+    adapts(FloatDomain)
+    signature = RoundSig()
+    domains = [FloatDomain()]
+    codomain = FloatDomain()
+
+
+class CorrelateRoundTo(Correlate):
+
+    adapts(Domain, Domain)
+
+
+class BindRoundTo(BindPolyFunction):
+
+    named(('round', 2))
+    signature = RoundToSig()
+    correlation = CorrelateRoundTo
+
+
+class CorrelateDecimalRoundTo(CorrelateRoundTo):
+
+    adapts_many((IntegerDomain, IntegerDomain),
+                (DecimalDomain, IntegerDomain))
+    signature = RoundToSig()
+    domains = [DecimalDomain(), IntegerDomain()]
+    codomain = DecimalDomain()
+
+
+class CorrelateLength(Correlate):
+
+    adapts(Domain)
+
+
+class BindLength(BindPolyFunction):
+
+    named('length')
+    signature = LengthSig()
+    correlation = CorrelateLength
+
+
+class CorrelateStringLength(CorrelateLength):
+
+    adapts_many(StringDomain,
+                UntypedDomain)
+    signature = StringLengthSig()
+    domains = [StringDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateContains(Correlate):
+
+    adapts(Domain, Domain)
+
+
+class BindContains(BindPolyFunction):
+
+    named('~')
+    signature = ContainsSig(+1)
+    correlation = CorrelateContains
+
+
+class CorrelateStringContains(CorrelateContains):
+
+    adapts_many((StringDomain, StringDomain),
+                (StringDomain, UntypedDomain),
+                (UntypedDomain, StringDomain),
+                (UntypedDomain, UntypedDomain))
+    signature = StringContainsSig(+1)
+    domains = [StringDomain(), StringDomain()]
+    codomain = BooleanDomain()
+
+
+class CorrelateNotContains(Correlate):
+
+    adapts(Domain, Domain)
+
+
+class BindNotContains(BindPolyFunction):
+
+    named('!~')
+    signature = ContainsSig(-1)
+    correlation = CorrelateNotContains
+
+
+class CorrelateStringNotContains(CorrelateNotContains):
+
+    adapts_many((StringDomain, StringDomain),
+                (StringDomain, UntypedDomain),
+                (UntypedDomain, StringDomain),
+                (UntypedDomain, UntypedDomain))
+    signature = StringContainsSig(-1)
+    domains = [StringDomain(), StringDomain()]
+    codomain = BooleanDomain()
+
+
+class BindHomoFunction(BindFunction):
+
+    codomain = None
+
+    def correlate(self, **arguments):
+        assert self.signature is not None
+        domains = []
+        for parameter in self.signature.parameters:
+            name = parameter.name
+            value = arguments[name]
+            if not parameter.is_list:
+                if value is not None:
+                    domains.append(value.domain)
+            else:
+                domains.extend(item.domain for item in value)
+        domain = coerce(*domains)
+        if domain is None:
+            raise BindError("incompatible arguments", self.syntax.mark)
+        cast_arguments = {}
+        for parameter in self.signature.parameters:
+            name = parameter.name
+            value = arguments[name]
+            if not parameter.is_list:
+                if value is not None:
+                    value = CastBinding(value, domain, value.syntax)
+            else:
+                value = [CastBinding(item, domain, item.syntax)
+                         for item in value]
+            cast_arguments[name] = value
+        if self.codomain is None:
+            codomain = domain
+        else:
+            codomain = coerce(self.codomain)
+        return FunctionBinding(self.signature, codomain, self.syntax,
+                               **cast_arguments)
+
+
+class BindIsNull(BindHomoFunction):
+
+    named('is_null')
+    signature = IsNullSig()
+    codomain = BooleanDomain()
+
+
+class BindNullIf(BindHomoFunction):
+
+    named('null_if')
+    signature = NullIfSig()
+
+
+class BindIfNull(BindHomoFunction):
+
+    named('if_null')
+    signature = IfNullSig()
+
+
+class BindIf(BindFunction):
+
+    named('if')
+    signature = IfSig()
+
+    def match(self):
+        operands = list(reversed(self.syntax.arguments))
+        if len(operands) < 2:
+            raise BindError("not enough arguments", self.syntax.mark)
+        predicates = []
+        consequents = []
+        alternative = None
+        while operands:
+            if len(operands) == 1:
+                alternative = operands.pop()
+            else:
+                predicates.append(operands.pop())
+                consequents.append(operands.pop())
+        return {
+                'predicates': predicates,
+                'consequents': consequents,
+                'alternative': alternative,
+        }
+
+    def correlate(self, predicates, consequents, alternative):
+        predicates = [CastBinding(predicate, coerce(BooleanDomain()),
+                                  predicate.syntax)
+                      for predicate in predicates]
+        domains = [consequent.domain for consequent in consequents]
+        if alternative is not None:
+            domains.append(alternative.domain)
+        domain = coerce(*domains)
+        if domain is None:
+            raise BindingError("incompatible arguments", self.syntax.mark)
+        consequents = [CastBinding(consequent, domain, consequent.syntax)
+                       for consequent in consequents]
+        if alternative is not None:
+            alternative = CastBinding(alternative, domain, consequent.syntax)
+        return FunctionBinding(self.signature, domain, self.syntax,
+                               predicates=predicates,
+                               consequents=consequents,
+                               alternative=alternative)
+
+
+class BindSwitch(BindFunction):
+
+    named('switch')
+    signature = SwitchSig()
+
+    def match(self):
+        operands = list(reversed(self.syntax.arguments))
+        if len(operands) < 3:
+            raise BindError("not enough arguments", self.syntax.mark)
+        variable = None
+        variants = []
+        consequents = []
+        alternative = None
+        variable = operands.pop()
+        while operands:
+            if len(operands) == 1:
+                alternative = operands.pop()
+            else:
+                variants.append(operands.pop())
+                consequents.append(operands.pop())
+        return {
+                'variable': variable,
+                'variants': variants,
+                'consequents': consequents,
+                'alternative': alternative,
+        }
+
+    def correlate(self, variable, variants, consequents, alternative):
+        domains = [variable.domain] + [variant.domain for variant in variants]
+        domain = coerce(*domains)
+        if domain is None:
+            raise BindError("incompatible arguments", self.syntax.mark)
+        variable = CastBinding(variable, domain, variable.syntax)
+        variants = [CastBinding(variant, domain, variant.syntax)
+                    for variant in variants]
+        domains = [consequent.domain for consequent in consequents]
+        if alternative is not None:
+            domains.append(alternative.domain)
+        domain = coerce(*domains)
+        if domain is None:
+            raise BindingError("incompatible arguments", self.syntax.mark)
+        consequents = [CastBinding(consequent, domain, consequent.syntax)
+                       for consequent in consequents]
+        if alternative is not None:
+            alternative = CastBinding(alternative, domain, consequent.syntax)
+        return FunctionBinding(self.signature, domain, self.syntax,
+                               variable=variable,
+                               variants=variants,
+                               consequents=consequents,
+                               alternative=alternative)
+
+
+class BindExistsBase(BindFunction):
+
+    signature = UnarySig()
+    bind_signature = None
+
+    def correlate(self, op):
+        op = CastBinding(op, coerce(BooleanDomain()), op.syntax)
+        return FunctionBinding(self.bind_signature, op.domain, self.syntax,
+                               base=self.state.base, op=op)
+
+
+class BindExists(BindExistsBase):
+
+    named('exists')
+    bind_signature = ExistsSig()
+
+
+class BindEvery(BindExistsBase):
+
+    named('every')
+    bind_signature = EverySig()
+
+
+class BindCount(BindFunction):
+
+    named('count')
+    signature = UnarySig()
+
+    def correlate(self, op):
+        op = CastBinding(op, coerce(BooleanDomain()), op.syntax)
+        return FunctionBinding(CountSig(), coerce(IntegerDomain()),
+                               self.syntax, base=self.state.base, op=op)
+
+
+class CorrelateAggregate(Adapter):
+
+    adapts(Domain)
+    signature = None
+    domain = None
+    codomain = None
+
+    def __call__(self):
+        return (self.signature is not None and
+                self.domain is not None and
+                self.codomain is not None)
+
+
+class BindPolyAggregate(BindFunction):
+
+    signature = UnarySig()
+    correlation = None
+
+    def correlate(self, op):
+        correlate = self.correlation(op.domain)
+        if not correlate():
+            raise BindError("incompatible argument", self.syntax.mark)
+        op = CastBinding(op, coerce(correlate.domain), op.syntax)
+        return FunctionBinding(correlate.signature, coerce(correlate.codomain),
+                               self.syntax, base=self.state.base, op=op)
+
+
+class CorrelateMin(CorrelateAggregate):
+
+    signature = MinSig()
+
+
+class BindMin(BindPolyAggregate):
+
+    named('min')
+    correlation = CorrelateMin
+
+
+class CorrelateIntegerMin(CorrelateMin):
+
+    adapts(IntegerDomain)
+    domain = IntegerDomain()
+    codomain = IntegerDomain()
+
+
+class CorrelateDecimalMin(CorrelateMin):
+
+    adapts(DecimalDomain)
+    domain = DecimalDomain()
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatMin(CorrelateMin):
+
+    adapts(FloatDomain)
+    domain = FloatDomain()
+    codomain = FloatDomain()
+
+
+class CorrelateStringMin(CorrelateMin):
+
+    adapts(StringDomain)
+    domain = StringDomain()
+    codomain = StringDomain()
+
+
+class CorrelateDateMin(CorrelateMin):
+
+    adapts(DateDomain)
+    domain = DateDomain()
+    codomain = DateDomain()
+
+
+class CorrelateMax(CorrelateAggregate):
+
+    signature = MaxSig()
+
+
+class BindMax(BindPolyAggregate):
+
+    named('max')
+    correlation = CorrelateMax
+
+
+class CorrelateIntegerMax(CorrelateMax):
+
+    adapts(IntegerDomain)
+    domain = IntegerDomain()
+    codomain = IntegerDomain()
+
+
+class CorrelateDecimalMax(CorrelateMax):
+
+    adapts(DecimalDomain)
+    domain = DecimalDomain()
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatMax(CorrelateMax):
+
+    adapts(FloatDomain)
+    domain = FloatDomain()
+    codomain = FloatDomain()
+
+
+class CorrelateStringMax(CorrelateMax):
+
+    adapts(StringDomain)
+    domain = StringDomain()
+    codomain = StringDomain()
+
+
+class CorrelateDateMax(CorrelateMax):
+
+    adapts(DateDomain)
+    domain = DateDomain()
+    codomain = DateDomain()
+
+
+class CorrelateSum(CorrelateAggregate):
+
+    signature = SumSig()
+
+
+class BindSum(BindPolyAggregate):
+
+    named('sum')
+    correlation = CorrelateSum
+
+
+class CorrelateIntegerSum(CorrelateSum):
+
+    adapts(IntegerDomain)
+    domain = IntegerDomain()
+    codomain = IntegerDomain()
+
+
+class CorrelateDecimalSum(CorrelateSum):
+
+    adapts(DecimalDomain)
+    domain = DecimalDomain()
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatSum(CorrelateSum):
+
+    adapts(FloatDomain)
+    domain = FloatDomain()
+    codomain = FloatDomain()
+
+
+class CorrelateAvg(CorrelateAggregate):
+
+    signature = AvgSig()
+
+
+class BindAvg(BindPolyAggregate):
+
+    named('avg')
+    correlation = CorrelateAvg
+
+
+class CorrelateDecimalAvg(CorrelateAvg):
+
+    adapts_many(IntegerDomain,
+                DecimalDomain)
+    domain = DecimalDomain()
+    codomain = DecimalDomain()
+
+
+class CorrelateFloatAvg(CorrelateAvg):
+
+    adapts(FloatDomain)
+    domain = FloatDomain()
+    codomain = FloatDomain()
+
+

src/htsql/tr/fn/encode.py

+#
+# Copyright (c) 2006-2010, Prometheus Research, LLC
+# Authors: Clark C. Evans <cce@clarkevans.com>,
+#          Kirill Simonov <xi@resolvent.net>
+#
+
+
+"""
+:mod:`htsql.tr.fn.encode`
+=========================
+"""
+
+
+from ...adapter import Adapter, adapts
+from ...domain import UntypedDomain
+from ..encode import EncodeBySignature
+from ..error import EncodeError
+from ..coerce import coerce
+from ..binding import LiteralBinding, CastBinding
+from ..code import (NegationCode, LiteralCode, ScalarUnit, CorrelatedUnit,
+                    AggregateUnit, FilteredSpace, FunctionCode)
+from .signature import (NullIfSig, IfNullSig, QuantifySig, WrapExistsSig,
+                        AggregateSig, CountSig,TakeCountSig,
+                        MinSig, TakeMinSig, MaxSig, TakeMaxSig,
+                        SumSig, TakeSumSig, AvgSig, TakeAvgSig)
+
+
+class EncodeQuantify(EncodeBySignature):
+
+    adapts(QuantifySig)
+
+    def __call__(self):
+        op = self.state.encode(self.binding.op)
+        if self.signature.polarity == -1:
+            op = NegationCode(op, self.binding)
+        space = self.state.relate(self.binding.base)
+        plural_units = [unit for unit in op.units
+                             if not space.spans(unit.space)]
+        if not plural_units:
+            raise EncodeError("a plural operand is required", op.mark)
+        plural_spaces = []
+        for unit in plural_units:
+            if any(plural_space.dominates(unit.space)
+                   for plural_space in plural_spaces):
+                continue
+            plural_spaces = [plural_space
+                             for plural_space in plural_spaces
+                             if not unit.space.dominates(plural_space)]
+            plural_spaces.append(unit.space)
+        if len(plural_spaces) > 1:
+            raise EncodeError("invalid plural operand", op.mark)
+        plural_space = plural_spaces[0]
+        if not plural_space.spans(space):
+            raise EncodeError("invalid plural operand", op.mark)
+        plural_space = FilteredSpace(plural_space, op, self.binding)
+        op = LiteralCode(True, op.domain, self.binding)
+        aggregate = CorrelatedUnit(op, plural_space, space,
+                                   self.binding)
+        wrapper = FunctionCode(WrapExistsSig(), op.domain, self.binding,
+                               op=aggregate)
+        if self.signature.polarity == -1:
+            wrapper = NegationCode(wrapper, self.binding)
+        wrapper = ScalarUnit(wrapper, space, self.binding)
+        return wrapper
+
+
+class EncodeAggregate(EncodeBySignature):
+
+    adapts(AggregateSig)
+
+    def take(self, op):
+        return op
+
+    def wrap(self, op):
+        return op
+
+    def __call__(self):
+        op = self.take(self.state.encode(self.binding.op))
+        space = self.state.relate(self.binding.base)
+        plural_units = [unit for unit in op.units
+                             if not space.spans(unit.space)]
+        if not plural_units:
+            raise EncodeError("a plural operand is required", op.mark)
+        plural_spaces = []
+        for unit in plural_units:
+            if any(plural_space.dominates(unit.space)
+                   for plural_space in plural_spaces):
+                continue
+            plural_spaces = [plural_space
+                             for plural_space in plural_spaces
+                             if not unit.space.dominates(plural_space)]
+            plural_spaces.append(unit.space)
+        if len(plural_spaces) > 1:
+            raise EncodeError("invalid plural operand", op.mark)
+        plural_space = plural_spaces[0]
+        if not plural_space.spans(space):
+            raise EncodeError("invalid plural operand", op.mark)
+        aggregate = AggregateUnit(op, plural_space, space, self.binding)
+        wrapper = self.wrap(aggregate)
+        wrapper = ScalarUnit(wrapper, space, self.binding)
+        return wrapper
+
+
+class EncodeCount(EncodeAggregate):
+
+    adapts(CountSig)
+
+    def take(self, op):
+        false = LiteralCode(False, op.domain, op.binding)
+        op = FunctionCode(NullIfSig(), op.domain, op.binding,
+                          lop=op, rops=[false])
+        return FunctionCode(TakeCountSig(), self.binding.domain, self.binding,
+                            op=op)
+
+    def wrap(self, op):
+        zero = LiteralBinding('0', UntypedDomain(), op.syntax)
+        zero = CastBinding(zero, op.domain, op.syntax)
+        zero = self.state.encode(zero)
+        return FunctionCode(IfNullSig(), op.domain, op.binding,
+                            lop=op, rops=[zero])
+
+
+class EncodeMin(EncodeAggregate):
+
+    adapts(MinSig)
+
+    def take(self, op):
+        return FunctionCode(TakeMinSig(), self.binding.domain, self.binding,
+                            op=op)
+
+
+class EncodeMax(EncodeAggregate):
+
+    adapts(MaxSig)
+
+    def take(self, op):
+        return FunctionCode(TakeMaxSig(), self.binding.domain, self.binding,
+                            op=op)
+
+
+class EncodeSum(EncodeAggregate):
+
+    adapts(SumSig)
+
+    def take(self, op):
+        return FunctionCode(TakeSumSig(), self.binding.domain, self.binding,
+                            op=op)
+
+    def wrap(self, op):
+        zero = LiteralBinding('0', UntypedDomain(), op.syntax)
+        zero = CastBinding(zero, op.domain, op.syntax)
+        zero = self.state.encode(zero)
+        return FunctionCode(IfNullSig(), op.domain, op.binding,
+                            lop=op, rops=[zero])
+
+
+class EncodeAvg(EncodeAggregate):
+
+    adapts(AvgSig)
+
+    def take(self, op):
+        return FunctionCode(TakeAvgSig(), self.binding.domain, self.binding,
+                            op=op)
+
+

src/htsql/tr/fn/function.py

-#
-# Copyright (c) 2006-2010, Prometheus Research, LLC
-# Authors: Clark C. Evans <cce@clarkevans.com>,
-#          Kirill Simonov <xi@resolvent.net>
-#
-
-
-"""
-:mod:`htsql.tr.fn.function`
-===========================
-
-This module implements HTSQL functions.
-"""
-
-
-from ...adapter import (Adapter, Utility, Protocol, adapts, adapts_none,
-                        adapts_many, named)
-from ...error import InvalidArgumentError
-from ...domain import (Domain, UntypedDomain, BooleanDomain, StringDomain,
-                       NumberDomain, IntegerDomain, DecimalDomain, FloatDomain,
-                       DateDomain)
-from ..syntax import NumberSyntax, StringSyntax, IdentifierSyntax
-from ..binding import (LiteralBinding, SortBinding, SieveBinding,
-                       FunctionBinding, EqualityBinding, TotalEqualityBinding,
-                       ConjunctionBinding, DisjunctionBinding, NegationBinding,
-                       CastBinding, WrapperBinding, TitleBinding,
-                       DirectionBinding)
-from ..encode import Encode
-from ..code import (FunctionCode, NegationCode, ScalarUnit, AggregateUnit,
-                    CorrelatedUnit, LiteralCode, FilteredSpace)
-from ..assemble import Evaluate
-from ..reduce import Reduce
-from ..frame import (FunctionPhrase, IsNullPhrase, NullIfPhrase, IfNullPhrase,
-                     LiteralPhrase, TruePhrase, FalsePhrase, NullPhrase)
-from ..serializer import Serializer, Format, Serialize
-from ..coerce import coerce
-from ..lookup import lookup
-
-
-class Function(Protocol):
-
-    def __init__(self, syntax, state):
-        self.syntax = syntax
-        self.state = state
-        self.mark = syntax.mark
-
-    @classmethod
-    def dispatch(self, syntax, *args, **kwds):
-        return syntax.name
-
-    def __call__(self):
-        raise InvalidArgumentError("unknown function or operator %s"
-                                   % self.syntax.name, self.mark)
-
-
-class Parameter(object):
-
-    def __init__(self, name, domain_class=Domain,
-                 is_mandatory=True, is_list=False):
-        assert isinstance(name, str)
-        assert issubclass(domain_class, Domain)
-        assert isinstance(is_mandatory, bool)
-        assert isinstance(is_list, bool)
-        self.name = name
-        self.domain_class = domain_class
-        self.is_mandatory = is_mandatory
-        self.is_list = is_list
-
-
-class ProperFunction(Function):
-
-    parameters = []
-
-    def __call__(self):
-        keywords = self.bind_arguments()
-        return self.correlate(**keywords)
-
-    def bind_arguments(self):
-        arguments = [list(self.state.bind_all(argument))
-                     for argument in self.syntax.arguments]
-        return self.check_arguments(arguments)
-
-    def check_arguments(self, arguments):
-        arguments = arguments[:]
-        keywords = {}
-        for idx, parameter in enumerate(self.parameters):
-            value = None
-            if not arguments:
-                if parameter.is_mandatory:
-                    raise InvalidArgumentError("missing argument %s"
-                                               % parameter.name, self.mark)
-            elif parameter.is_list:
-                value = []
-                if len(arguments) > 1 and idx == len(self.parameters)-1:
-                    while arguments:
-                        argument = arguments.pop(0)
-                        if len(argument) != 1:
-                            raise InvalidArgumentError("invalid argument %s"
-                                                       % parameter.name,
-                                                       self.mark)
-                        value.append(argument[0])
-                else:
-                    argument = arguments.pop(0)
-                    if parameter.is_mandatory and not argument:
-                        raise InvalidArgumentError("missing argument %s"
-                                                   % parameter.name,
-                                                   self.mark)
-                    value = argument[:]
-                for argument in value:
-                    if not isinstance(argument.domain, parameter.domain_class):
-                        raise InvalidArgumentError("unexpected argument type",
-                                                   argument.mark)
-            else:
-                argument = arguments.pop(0)
-                if len(argument) == 0:
-                    if parameter.is_mandatory:
-                        raise InvalidArgumentError("missing argument %s"
-                                                   % parameter.name,
-                                                   self.mark)
-                    value = None
-                elif len(argument) == 1:
-                    value = argument[0]
-                    if not isinstance(value.domain, parameter.domain_class):
-                        raise InvalidArgumentError("unexpected argument type",
-                                                   value.mark)
-                else:
-                    raise InvalidArgumentError("too many arguments",
-                                               argument[1].mark)
-            keywords[parameter.name] = value
-        while arguments:
-            argument = arguments.pop(0)
-            if argument:
-                raise InvalidArgumentError("unexpected argument",
-                                           argument[0].mark)
-        return keywords
-
-
-class ProperMethod(ProperFunction):
-
-    def bind_arguments(self):
-        arguments = ([[self.state.base]] +
-                     [list(self.state.bind_all(argument))
-                      for argument in self.syntax.arguments])
-        return self.check_arguments(arguments)
-
-
-class RootFunction(Function):
-
-    named('root')
-
-    def __call__(self):
-        if len(self.syntax.arguments) != 0:
-            raise InvalidArgumentError("unexpected arguments",
-                                       self.syntax.mark)
-        yield WrapperBinding(self.state.root, self.syntax)
-
-
-class ThisFunction(Function):
-