Commits

Kirill Simonov committed 3c3b1c1

Added missing string and date functions.

  • Participants
  • Parent commits 6a0c3d4

Comments (0)

Files changed (11)

src/htsql/tr/encode.py

     Convert an expression to a string.
     """
 
-    adapts_many((NumberDomain, StringDomain),
+    adapts_many((BooleanDomain, StringDomain),
+                (NumberDomain, StringDomain),
                 (EnumDomain, StringDomain),
                 (DateDomain, StringDomain),
                 (OpaqueDomain, StringDomain))

src/htsql/tr/fn/bind.py

 from ..coerce import coerce
 from ..lookup import lookup
 from .signature import (Signature, UnarySig, BinarySig, ThisSig, RootSig,
-                        DirectSig, FiberSig, AsSig, SortDirectionSig, LimitSig,
-                        SortSig, NullSig, TrueSig, FalseSig, CastSig, DateSig,
+                        FiberSig, AsSig, SortDirectionSig, LimitSig,
+                        SortSig, NullSig, TrueSig, FalseSig, CastSig,
+                        MakeDateSig, ExtractYearSig, ExtractMonthSig,
+                        ExtractDaySig,
                         IsEqualSig, IsInSig, IsTotallyEqualSig, AndSig, OrSig,
                         NotSig, CompareSig, AddSig, ConcatenateSig,
+                        HeadSig, TailSig, SliceSig, AtSig, ReplaceSig,
+                        UpperSig, LowerSig, TrimSig,
                         DateIncrementSig, SubtractSig, DateDecrementSig,
-                        DateDifferenceSig,
+                        DateDifferenceSig, TodaySig,
                         MultiplySig, DivideSig, IsNullSig, NullIfSig,
                         IfNullSig, IfSig, SwitchSig, KeepPolaritySig,
                         ReversePolaritySig, RoundSig, RoundToSig, LengthSig,
         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 = FormulaBinding(IsEqualSig(+1), coerce(BooleanDomain()),
-                                   self.syntax, lop=parent, rop=child)
-        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):
-
-    signature = SortDirectionSig
-    direction = None
-
-    def expand(self, base):
-        for base in self.state.bind_all(base):
-            yield DirectionBinding(base, self.direction, self.syntax)
-
-
-class BindAscDir(BindDirectionBase):
-
-    named('_+')
-    direction = +1
-
-
-class BindDescDir(BindDirectionBase):
-
-    named('_-')
-    direction = -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):
 
     signature = None
         assert self.codomain is not None
         cast_arguments = {}
         for domain, slot in zip(self.domains, self.signature.slots):
-            domain = coerce(domain)
             name = slot.name
             value = arguments[name]
             if slot.is_singular:
                                self.syntax, **cast_arguments)
 
 
-class BindDate(BindMonoFunction):
+class BindHomoFunction(BindFunction):
 
-    named(('date', 3))
-    signature = DateSig
-    domains = [IntegerDomain(), IntegerDomain(), IntegerDomain()]
-    codomain = DateDomain()
+    codomain = None
 
-
-class BindAmongBase(BindFunction):
-
-    signature = IsInSig
-    polarity = None
-
-    def correlate(self, lop, rops):
-        domain = coerce(lop.domain, *(rop.domain for rop in rops))
+    def correlate(self, **arguments):
+        assert self.signature is not None
+        domains = []
+        for slot in self.signature.slots:
+            name = slot.name
+            value = arguments[name]
+            if slot.is_singular:
+                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)
-        lop = CastBinding(lop, domain, lop.syntax)
-        rops = [CastBinding(rop, domain, rop.syntax) for rop in rops]
-        if len(rops) == 1:
-            return FormulaBinding(IsEqualSig(self.polarity),
-                                  coerce(BooleanDomain()),
-                                  self.syntax, lop=lop, rop=rops[0])
+        cast_arguments = {}
+        for slot in self.signature.slots:
+            name = slot.name
+            value = arguments[name]
+            if slot.is_singular:
+                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:
-            return FormulaBinding(self.signature(self.polarity),
-                                  coerce(BooleanDomain()),
-                                  self.syntax, lop=lop, rops=rops)
-
-
-class BindAmong(BindAmongBase):
-
-    named('=')
-    polarity = +1
-
-
-class BindNotAmong(BindAmongBase):
-
-    named('!=')
-    polarity = -1
-
-
-class BindTotallyEqualBase(BindFunction):
-
-    signature = IsTotallyEqualSig
-    polarity = None
-
-    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)
-        return FormulaBinding(IsTotallyEqualSig(self.polarity),
-                              coerce(BooleanDomain()), self.syntax,
-                              lop=lop, rop=rop)
-
-
-class BindTotallyEqual(BindTotallyEqualBase):
-
-    named('==')
-    polarity = +1
-
-
-class BindTotallyNotEqual(BindTotallyEqualBase):
-
-    named('!==')
-    polarity = -1
-
-
-class BindAnd(BindFunction):
-
-    named('&')
-    signature = BinarySig
-
-    def correlate(self, lop, rop):
-        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 = BinarySig
-
-    def correlate(self, lop, rop):
-        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):
-
-    named('!_')
-    signature = NotSig
-
-    def correlate(self, op):
-        domain = coerce(BooleanDomain())
-        op = CastBinding(op, domain, op.syntax)
-        return FormulaBinding(self.signature(), domain, self.syntax, op=op)
-
-
-class BindCompare(BindFunction):
-
-    signature = CompareSig
-    relation = None
-
-    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 FormulaBinding(self.signature(self.relation),
-                              coerce(BooleanDomain()),
-                              self.syntax, lop=lop, rop=rop)
-
-
-class BindLessThan(BindCompare):
-
-    named('<')
-    relation = '<'
-
-
-class BindLessThanOrEqual(BindCompare):
-
-    named('<=')
-    relation = '<='
-
-
-class BindGreaterThan(BindCompare):
-
-    named('>')
-    relation = '>'
-
-
-class BindGreaterThanOrEqual(BindCompare):
-
-    named('>=')
-    relation = '>='
-
-
-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
+            codomain = coerce(self.codomain)
+        return FormulaBinding(self.signature, codomain, self.syntax,
+                               **cast_arguments)
 
 
 class Correlate(Component):
     input_domains = []
     input_arity = 0
 
-    signature = None
-    domains = []
-    codomain = None
-
-    hint = None
-    help = None
-
     @classmethod
     def dominates(component, other):
         if component.input_signature is None:
         self.arguments = binding.arguments
 
     def __call__(self):
-        if self.signature is None:
-            raise BindError("incompatible arguments", self.binding.mark)
-        signature = self.binding.signature.clone_to(self.signature)
+        raise BindError("incompatible arguments", self.binding.mark)
+
+
+def correlates(signature, *domain_vectors):
+    assert issubclass(signature, Signature)
+    domain_vectors = [domain_vector if isinstance(domain_vector, tuple)
+                                  else (domain_vector,)
+                      for domain_vector in domain_vectors]
+    assert len(domain_vectors) > 0
+    arity = len(domain_vectors[0])
+    assert all(len(domain_vector) == arity
+               for domain_vector in domain_vectors)
+    frame = sys._getframe(1)
+    frame.f_locals['input_signature'] = signature
+    frame.f_locals['input_domains'] = domain_vectors
+    frame.f_locals['input_arity'] = arity
+
+
+def correlates_none():
+    frame = sys._getframe(1)
+    frame.f_locals['input_signature'] = None
+    frame.f_locals['input_domains'] = []
+    frame.f_locals['input_arity'] = 0
+
+
+class CorrelateFunction(Correlate):
+
+    correlates_none()
+    signature = None
+    domains = []
+    codomain = None
+
+    hint = None
+    help = None
+
+    def __call__(self):
+        signature = self.binding.signature
+        if self.signature is not None:
+            signature = signature.clone_to(self.signature)
         assert self.arguments.admits(Binding, signature)
         arguments = {}
         for index, slot in enumerate(signature.slots):
                               **arguments)
 
 
-def correlates(signature, *domain_vectors):
-    assert issubclass(signature, Signature)
-    domain_vectors = [domain_vector if isinstance(domain_vector, tuple)
-                                  else (domain_vector,)
-                      for domain_vector in domain_vectors]
-    assert len(domain_vectors) > 0
-    arity = len(domain_vectors[0])
-    assert all(len(domain_vector) == arity
-               for domain_vector in domain_vectors)
-    frame = sys._getframe(1)
-    frame.f_locals['input_signature'] = signature
-    frame.f_locals['input_domains'] = domain_vectors
-    frame.f_locals['input_arity'] = arity
-
-
-def correlates_none():
-    frame = sys._getframe(1)
-    frame.f_locals['input_signature'] = None
-    frame.f_locals['input_domains'] = []
-    frame.f_locals['input_arity'] = 0
-
-
 class BindPolyFunction(BindFunction):
 
     signature = None
         return correlate()
 
 
+
+
+
+
+
+
+
+
+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 BindFiber(BindMacro):
+
+    named('fiber')
+    signature = FiberSig
+
+    def expand(self, table, image=None, 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)
+        if image is None and counterimage is None:
+            yield WrapperBinding(binding, self.syntax)
+            return
+        if image is None:
+            image = counterimage
+        if counterimage is None:
+            counterimage = image
+        parent = self.state.bind(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 = FormulaBinding(IsEqualSig(+1), coerce(BooleanDomain()),
+                                   self.syntax, lop=parent, rop=child)
+        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):
+
+    signature = SortDirectionSig
+    direction = None
+
+    def expand(self, base):
+        for base in self.state.bind_all(base):
+            yield DirectionBinding(base, self.direction, self.syntax)
+
+
+class BindAscDir(BindDirectionBase):
+
+    named('_+')
+    direction = +1
+
+
+class BindDescDir(BindDirectionBase):
+
+    named('_-')
+    direction = -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 BindMakeDate(BindMonoFunction):
+
+    named(('date', 3))
+    signature = MakeDateSig
+    domains = [IntegerDomain(), IntegerDomain(), IntegerDomain()]
+    codomain = DateDomain()
+
+
+class BindAmongBase(BindFunction):
+
+    signature = IsInSig
+    polarity = None
+
+    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:
+            return FormulaBinding(IsEqualSig(self.polarity),
+                                  coerce(BooleanDomain()),
+                                  self.syntax, lop=lop, rop=rops[0])
+        else:
+            return FormulaBinding(self.signature(self.polarity),
+                                  coerce(BooleanDomain()),
+                                  self.syntax, lop=lop, rops=rops)
+
+
+class BindAmong(BindAmongBase):
+
+    named('=')
+    polarity = +1
+
+
+class BindNotAmong(BindAmongBase):
+
+    named('!=')
+    polarity = -1
+
+
+class BindTotallyEqualBase(BindFunction):
+
+    signature = IsTotallyEqualSig
+    polarity = None
+
+    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)
+        return FormulaBinding(IsTotallyEqualSig(self.polarity),
+                              coerce(BooleanDomain()), self.syntax,
+                              lop=lop, rop=rop)
+
+
+class BindTotallyEqual(BindTotallyEqualBase):
+
+    named('==')
+    polarity = +1
+
+
+class BindTotallyNotEqual(BindTotallyEqualBase):
+
+    named('!==')
+    polarity = -1
+
+
+class BindAnd(BindFunction):
+
+    named('&')
+    signature = BinarySig
+
+    def correlate(self, lop, rop):
+        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 = BinarySig
+
+    def correlate(self, lop, rop):
+        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):
+
+    named('!_')
+    signature = NotSig
+
+    def correlate(self, op):
+        domain = coerce(BooleanDomain())
+        op = CastBinding(op, domain, op.syntax)
+        return FormulaBinding(self.signature(), domain, self.syntax, op=op)
+
+
+class BindCompare(BindFunction):
+
+    signature = CompareSig
+    relation = None
+
+    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 FormulaBinding(self.signature(self.relation),
+                              coerce(BooleanDomain()),
+                              self.syntax, lop=lop, rop=rop)
+
+
+class BindLessThan(BindCompare):
+
+    named('<')
+    relation = '<'
+
+
+class BindLessThanOrEqual(BindCompare):
+
+    named('<=')
+    relation = '<='
+
+
+class BindGreaterThan(BindCompare):
+
+    named('>')
+    relation = '>'
+
+
+class BindGreaterThanOrEqual(BindCompare):
+
+    named('>=')
+    relation = '>='
+
+
+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 BindAdd(BindPolyFunction):
 
     named('+')
     signature = AddSig
 
 
-class CorrelateIntegerAdd(Correlate):
+class CorrelateIntegerAdd(CorrelateFunction):
 
     correlates(AddSig, (IntegerDomain, IntegerDomain))
     signature = AddSig
     codomain = IntegerDomain()
 
 
-class CorrelateDecimalAdd(Correlate):
+class CorrelateDecimalAdd(CorrelateFunction):
 
     correlates(AddSig, (IntegerDomain, DecimalDomain),
                        (DecimalDomain, IntegerDomain),
     codomain = DecimalDomain()
 
 
-class CorrelateFloatAdd(Correlate):
+class CorrelateFloatAdd(CorrelateFunction):
 
     correlates(AddSig, (IntegerDomain, FloatDomain),
                        (DecimalDomain, FloatDomain),
     codomain = FloatDomain()
 
 
-class CorrelateDateIncrement(Correlate):
+class CorrelateDateIncrement(CorrelateFunction):
 
     correlates(AddSig, (DateDomain, IntegerDomain))
     signature = DateIncrementSig
     codomain = DateDomain()
 
 
-class CorrelateConcatenate(Correlate):
+class CorrelateConcatenate(CorrelateFunction):
 
     correlates(AddSig, (UntypedDomain, UntypedDomain),
                        (UntypedDomain, StringDomain),
     signature = SubtractSig
 
 
-class CorrelateIntegerSubtract(Correlate):
+class CorrelateIntegerSubtract(CorrelateFunction):
 
     correlates(SubtractSig, (IntegerDomain, IntegerDomain))
     signature = SubtractSig
     codomain = IntegerDomain()
 
 
-class CorrelateDecimalSubtract(Correlate):
+class CorrelateDecimalSubtract(CorrelateFunction):
 
     correlates(SubtractSig, (IntegerDomain, DecimalDomain),
                             (DecimalDomain, IntegerDomain),
     codomain = DecimalDomain()
 
 
-class CorrelateFloatSubtract(Correlate):
+class CorrelateFloatSubtract(CorrelateFunction):
 
     correlates(SubtractSig, (IntegerDomain, FloatDomain),
                             (DecimalDomain, FloatDomain),
     codomain = FloatDomain()
 
 
-class CorrelateDateDecrement(Correlate):
+class CorrelateDateDecrement(CorrelateFunction):
 
     correlates(SubtractSig, (DateDomain, IntegerDomain))
     signature = DateDecrementSig
     codomain = DateDomain()
 
 
-class CorrelateDateDifference(Correlate):
+class CorrelateDateDifference(CorrelateFunction):
 
     correlates(SubtractSig, (DateDomain, DateDomain))
     signature = DateDifferenceSig
     signature = MultiplySig
 
 
-class CorrelateIntegerMultiply(Correlate):
+class CorrelateIntegerMultiply(CorrelateFunction):
 
     correlates(MultiplySig, (IntegerDomain, IntegerDomain))
     signature = MultiplySig
     codomain = IntegerDomain()
 
 
-class CorrelateDecimalMultiply(Correlate):
+class CorrelateDecimalMultiply(CorrelateFunction):
 
     correlates(MultiplySig, (IntegerDomain, DecimalDomain),
                             (DecimalDomain, IntegerDomain),
     codomain = DecimalDomain()
 
 
-class CorrelateFloatMultiply(Correlate):
+class CorrelateFloatMultiply(CorrelateFunction):
 
     correlates(MultiplySig, (IntegerDomain, FloatDomain),
                             (DecimalDomain, FloatDomain),
     signature = DivideSig
 
 
-class CorrelateDecimalDivide(Correlate):
+class CorrelateDecimalDivide(CorrelateFunction):
 
     correlates(DivideSig, (IntegerDomain, IntegerDomain),
                           (IntegerDomain, DecimalDomain),
     codomain = DecimalDomain()
 
 
-class CorrelateFloatDivide(Correlate):
+class CorrelateFloatDivide(CorrelateFunction):
 
     correlates(DivideSig, (IntegerDomain, FloatDomain),
                           (DecimalDomain, FloatDomain),
     signature = KeepPolaritySig
 
 
-class CorrelateIntegerKeepPolarity(Correlate):
+class CorrelateIntegerKeepPolarity(CorrelateFunction):
 
     correlates(KeepPolaritySig, IntegerDomain)
     signature = KeepPolaritySig
     codomain = IntegerDomain()
 
 
-class CorrelateDecimalKeepPolarity(Correlate):
+class CorrelateDecimalKeepPolarity(CorrelateFunction):
 
     correlates(KeepPolaritySig, DecimalDomain)
     signature = KeepPolaritySig
     codomain = DecimalDomain()
 
 
-class CorrelateFloatKeepPolarity(Correlate):
+class CorrelateFloatKeepPolarity(CorrelateFunction):
 
     correlates(KeepPolaritySig, FloatDomain)
     signature = KeepPolaritySig
     signature = ReversePolaritySig
 
 
-class CorrelateIntegerReversePolarity(Correlate):
+class CorrelateIntegerReversePolarity(CorrelateFunction):
 
     correlates(ReversePolaritySig, IntegerDomain)
     signature = ReversePolaritySig
     codomain = IntegerDomain()
 
 
-class CorrelateDecimalReversePolarity(Correlate):
+class CorrelateDecimalReversePolarity(CorrelateFunction):
 
     correlates(ReversePolaritySig, DecimalDomain)
     signature = ReversePolaritySig
     codomain = DecimalDomain()
 
 
-class CorrelateFloatReversePolarity(Correlate):
+class CorrelateFloatReversePolarity(CorrelateFunction):
 
     correlates(ReversePolaritySig, FloatDomain)
     signature = ReversePolaritySig
     signature = RoundSig
 
 
-class CorrelateDecimalRound(Correlate):
+class CorrelateDecimalRound(CorrelateFunction):
 
     correlates(RoundSig, IntegerDomain,
                          DecimalDomain)
     codomain = DecimalDomain()
 
 
-class CorrelateFloatRound(Correlate):
+class CorrelateFloatRound(CorrelateFunction):
 
     correlates(RoundSig, FloatDomain)
     signature = RoundSig
     signature = RoundToSig
 
 
-class CorrelateDecimalRoundTo(Correlate):
+class CorrelateDecimalRoundTo(CorrelateFunction):
 
     correlates(RoundToSig, (IntegerDomain, IntegerDomain),
                            (DecimalDomain, IntegerDomain))
     signature = LengthSig
 
 
-class CorrelateStringLength(Correlate):
+class CorrelateStringLength(CorrelateFunction):
 
     correlates(LengthSig, StringDomain,
                           UntypedDomain)
     polarity = -1
 
 
-class CorrelateStringContains(Correlate):
+class CorrelateStringContains(CorrelateFunction):
 
     correlates(ContainsSig, (StringDomain, StringDomain),
                             (StringDomain, UntypedDomain),
     codomain = BooleanDomain()
 
 
-class BindHomoFunction(BindFunction):
+class BindHeadTailBase(BindPolyFunction):
 
-    codomain = None
+    signature = None
 
-    def correlate(self, **arguments):
-        assert self.signature is not None
-        domains = []
-        for slot in self.signature.slots:
-            name = slot.name
-            value = arguments[name]
-            if slot.is_singular:
-                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 slot in self.signature.slots:
-            name = slot.name
-            value = arguments[name]
-            if slot.is_singular:
-                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 FormulaBinding(self.signature, codomain, self.syntax,
-                               **cast_arguments)
+    def correlate(self, op, length):
+        if length is not None:
+            length = CastBinding(length, coerce(IntegerDomain()), length.syntax)
+        binding = FormulaBinding(self.signature(), UntypedDomain(),
+                                 self.syntax, op=op, length=length)
+        correlate = Correlate(binding, self.state)
+        return correlate()
+
+
+class BindHead(BindPolyFunction):
+
+    named('head')
+    signature = HeadSig
+
+
+class BindTail(BindPolyFunction):
+
+    named('tail')
+    signature = TailSig
+
+
+class CorrelateHead(CorrelateFunction):
+
+    correlates(HeadSig, UntypedDomain,
+                        StringDomain)
+    domains = [StringDomain()]
+    codomain = StringDomain()
+
+
+class CorrelateTail(CorrelateFunction):
+
+    correlates(TailSig, UntypedDomain,
+                        StringDomain)
+    domains = [StringDomain()]
+    codomain = StringDomain()
+
+
+class BindSlice(BindPolyFunction):
+
+    named('slice')
+    signature = SliceSig
+
+    def correlate(self, op, left, right):
+        if left is not None:
+            left = CastBinding(left, coerce(IntegerDomain()), left.syntax)
+        if right is not None:
+            right = CastBinding(right, coerce(IntegerDomain()), right.syntax)
+        binding = FormulaBinding(self.signature(), UntypedDomain(),
+                                 self.syntax, op=op, left=left, right=right)
+        correlate = Correlate(binding, self.state)
+        return correlate()
+
+
+class CorrelateSlice(CorrelateFunction):
+
+    correlates(SliceSig, UntypedDomain,
+                         StringDomain)
+    domains = [StringDomain()]
+    codomain = StringDomain()
+
+
+class BindAt(BindPolyFunction):
+
+    named('at')
+    signature = AtSig
+
+    def correlate(self, op, index, length):
+        index = CastBinding(index, coerce(IntegerDomain()), index.syntax)
+        if length is not None:
+            length = CastBinding(length, coerce(IntegerDomain()),
+                                 length.syntax)
+        binding = FormulaBinding(self.signature(), UntypedDomain(),
+                                 self.syntax, op=op, index=index, length=length)
+        correlate = Correlate(binding, self.state)
+        return correlate()
+
+
+class CorrelateAt(CorrelateFunction):
+
+    correlates(AtSig, UntypedDomain,
+                      StringDomain)
+    domains = [StringDomain()]
+    codomain = StringDomain()
+
+
+class BindReplace(BindPolyFunction):
+
+    named('replace')
+    signature = ReplaceSig
+
+
+class CorrelateReplace(CorrelateFunction):
+
+    correlates(ReplaceSig, UntypedDomain,
+                           StringDomain)
+    domains = [StringDomain(), StringDomain(), StringDomain()]
+    codomain = StringDomain()
+
+
+class BindUpper(BindPolyFunction):
+
+    named('upper')
+    signature = UpperSig
+
+
+class CorrelateUpper(CorrelateFunction):
+
+    correlates(UpperSig, UntypedDomain,
+                         StringDomain)
+    domains = [StringDomain()]
+    codomain = StringDomain()
+
+
+class BindLower(BindPolyFunction):
+
+    named('lower')
+    signature = LowerSig
+
+
+class CorrelateLower(CorrelateFunction):
+
+    correlates(LowerSig, UntypedDomain,
+                         StringDomain)
+    domains = [StringDomain()]
+    codomain = StringDomain()
+
+
+class BindTrimBase(BindPolyFunction):
+
+    signature = TrimSig
+    is_left = False
+    is_right = False
+
+    def correlate(self, op):
+        signature = self.signature(is_left=self.is_left,
+                                   is_right=self.is_right)
+        binding = FormulaBinding(signature, UntypedDomain(), self.syntax, op=op)
+        correlate = Correlate(binding, self.state)
+        return correlate()
+
+
+class BindTrim(BindTrimBase):
+
+    named('trim')
+    is_left = True
+    is_right = True
+
+
+class BindLTrim(BindTrimBase):
+
+    named('ltrim')
+    is_left = True
+
+
+class BindRTrim(BindTrimBase):
+
+    named('rtrim')
+    is_right = True
+
+
+class BindToday(BindMonoFunction):
+
+    named('today')
+    signature = TodaySig
+    codomain = DateDomain()
+
+
+class BindExtractYear(BindPolyFunction):
+
+    named('year')
+    signature = ExtractYearSig
+
+
+class BindExtractMonth(BindPolyFunction):
+
+    named('month')
+    signature = ExtractMonthSig
+
+
+class BindExtractDay(BindPolyFunction):
+
+    named('day')
+    signature = ExtractDaySig
+
+
+class CorrelateExtractYear(CorrelateFunction):
+
+    correlates(ExtractYearSig, DateDomain)
+    domains = [DateDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateExtractMonth(CorrelateFunction):
+
+    correlates(ExtractMonthSig, DateDomain)
+    domains = [DateDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateExtractDay(CorrelateFunction):
+
+    correlates(ExtractDaySig, DateDomain)
+    domains = [DateDomain()]
+    codomain = IntegerDomain()
+
+
+class CorrelateTrim(CorrelateFunction):
+
+    correlates(TrimSig, UntypedDomain,
+                        StringDomain)
+    domains = [StringDomain()]
+    codomain = StringDomain()
 
 
 class BindIsNull(BindHomoFunction):
     def correlate(self, op):
         binding = FormulaBinding(self.signature(), self.codomain, self.syntax,
                                  op=op)
-        correlate = CorrelateAggregate(binding, self.state)
+        correlate = Correlate(binding, self.state)
         return correlate()
 
 
-class CorrelateAggregate(Correlate):
+class CorrelateAggregate(CorrelateFunction):
 
     correlates_none()
     signature = None
     def correlate(self, op):
         binding = FormulaBinding(self.signature(self.polarity), self.codomain,
                                  self.syntax, op=op)
-        correlate = CorrelateAggregate(binding, self.state)
+        correlate = Correlate(binding, self.state)
         return correlate()
 
 

src/htsql/tr/fn/dump.py

 from .signature import (AddSig, ConcatenateSig, DateIncrementSig,
                         SubtractSig, DateDecrementSig, DateDifferenceSig,
                         MultiplySig, DivideSig, IfSig, SwitchSig,
-                        ReversePolaritySig,
-                        RoundSig, RoundToSig, LengthSig,
+                        ReversePolaritySig, RoundSig, RoundToSig,
+                        LengthSig, LikeSig, ReplaceSig, SubstringSig,
+                        UpperSig, LowerSig, TrimSig, TodaySig, MakeDateSig,
+                        ExtractYearSig, ExtractMonthSig, ExtractDaySig,
                         ExistsSig, CountSig, MinMaxSig, SumSig, AvgSig)
 
 
     template = "({lop} / {rop})"
 
 
+class DumpDateIncrement(DumpFunction):
+
+    adapts(DateIncrementSig)
+    template = "CAST({lop} + {rop} * INTERVAL '1' DAY AS DATE)"
+
+
+class DumpDateDecrement(DumpFunction):
+
+    adapts(DateDecrementSig)
+    template = "CAST({lop} - {rop} * INTERVAL '1' DAY AS DATE)"
+
+
+class DumpDateDifference(DumpFunction):
+
+    adapts(DateDifferenceSig)
+    template = "EXTRACT(DAY FROM {lop} - {rop})"
+
+
 class DumpConcatenate(DumpFunction):
 
     adapts(ConcatenateSig)
     template = "CHARACTER_LENGTH({op})"
 
 
+class DumpLike(DumpFunction):
+
+    adapts(LikeSig)
+
+    def __call__(self):
+        self.format("({lop} {polarity:not}LIKE {rop} ESCAPE {escape:literal})",
+                    self.arguments, self.signature, escape="\\")
+
+
+class DumpReplace(DumpFunction):
+
+    adapts(ReplaceSig)
+    template = "REPLACE({op}, {old}, {new})"
+
+
+class DumpSubstring(DumpFunction):
+
+    adapts(SubstringSig)
+
+    def __call__(self):
+        if self.phrase.length is not None:
+            self.format("SUBSTRING({op} FROM {start} FOR {length})",
+                        self.phrase)
+        else:
+            self.format("SUBSTRING({op} FROM {start})", self.arguments)
+
+
+class DumpUpper(DumpFunction):
+
+    adapts(UpperSig)
+    template = "UPPER({op})"
+
+
+class DumpLower(DumpFunction):
+
+    adapts(LowerSig)
+    template = "LOWER({op})"
+
+
+class DumpTrim(DumpFunction):
+
+    adapts(TrimSig)
+
+    def __call__(self):
+        if self.signature.is_left and not self.signature.is_right:
+            self.format("TRIM(LEADING FROM {op})", self.arguments)
+        elif not self.signature.is_left and self.signature.is_right:
+            self.format("TRIM(TRAILING FROM {op})", self.arguments)
+        else:
+            self.format("TRIM({op})", self.arguments)
+
+
+class DumpToday(DumpFunction):
+
+    adapts(TodaySig)
+    template = "CURRENT_DATE"
+
+
+class DumpMakeDate(DumpFunction):
+
+    adapts(MakeDateSig)
+    template = ("CAST(DATE '2001-01-01' + ({year} - 2001) * INTERVAL '1' YEAR"
+                " + ({month} - 1) * INTERVAL '1' MONTH"
+                " + ({day} - 1) * INTERVAL '1' DAY AS DATE)")
+
+
+class DumpExtractYear(DumpFunction):
+
+    adapts(ExtractYearSig)
+    template = "EXTRACT(YEAR FROM {op})"
+
+
+class DumpExtractMonth(DumpFunction):
+
+    adapts(ExtractMonthSig)
+    template = "EXTRACT(MONTH FROM {op})"
+
+
+class DumpExtractDay(DumpFunction):
+
+    adapts(ExtractDaySig)
+    template = "EXTRACT(DAY FROM {op})"
+
+
 class DumpExists(DumpFunction):
 
     adapts(ExistsSig)

src/htsql/tr/fn/encode.py

 """
 
 
-from ...adapter import Adapter, adapts, adapts_many
-from ...domain import UntypedDomain, BooleanDomain
+from ...adapter import Adapter, adapts, adapts_many, adapts_none
+from ...domain import UntypedDomain, BooleanDomain, IntegerDomain
 from ..encode import EncodeBySignature, EncodingState
 from ..error import EncodeError
 from ..coerce import coerce
                     AggregateUnit, FilteredSpace, FormulaCode)
 from .signature import (Signature, NotSig, NullIfSig, IfNullSig, QuantifySig,
                         ExistsSig, AggregateSig, QuantifySig,
-                        CountSig, SumSig)
+                        CountSig, SumSig, ReplaceSig, ConcatenateSig,
+                        LikeSig, ContainsSig, HeadSig, TailSig, SliceSig, AtSig,
+                        SubstringSig, LengthSig, AddSig, SubtractSig,
+                        CompareSig, IfSig, ReversePolaritySig)
 
 
-class EncodeAggregate(EncodeBySignature):
+class EncodeFunction(EncodeBySignature):
+
+    adapts_none()
+
+
+class EncodeContains(EncodeFunction):
+
+    adapts(ContainsSig)
+
+    def __call__(self):
+        lop = self.state.encode(self.binding.lop)
+        rop = self.state.encode(self.binding.rop)
+        if isinstance(rop, LiteralCode):
+            if rop.value is not None:
+                value = ("%" + rop.value.replace("\\", "\\\\")
+                                        .replace("%", "\\%")
+                                        .replace("_", "\\_") + "%")
+                rop = rop.clone(value=value)
+        else:
+            backslash_literal = LiteralCode("\\", rop.domain, self.binding)
+            xbackslash_literal = LiteralCode("\\\\", rop.domain, self.binding)
+            percent_literal = LiteralCode("%", rop.domain, self.binding)
+            xpercent_literal = LiteralCode("\\%", rop.domain, self.binding)
+            underscore_literal = LiteralCode("_", rop.domain, self.binding)
+            xunderscore_literal = LiteralCode("\\_", rop.domain, self.binding)
+            rop = FormulaCode(ReplaceSig(), rop.domain, self.binding,
+                              op=rop, old=backslash_literal,
+                              new=xbackslash_literal)
+            rop = FormulaCode(ReplaceSig(), rop.domain, self.binding,
+                              op=rop, old=percent_literal,
+                              new=xpercent_literal)
+            rop = FormulaCode(ReplaceSig(), rop.domain, self.binding,
+                              op=rop, old=underscore_literal,
+                              new=xunderscore_literal)
+            rop = FormulaCode(ConcatenateSig(), rop.domain, self.binding,
+                              lop=percent_literal, rop=rop)
+            rop = FormulaCode(ConcatenateSig(), rop.domain, self.binding,
+                              lop=rop, rop=percent_literal)
+        return FormulaCode(self.signature.clone_to(LikeSig),
+                           self.domain, self.binding, lop=lop, rop=rop)
+
+
+class EncodeHead(EncodeFunction):
+
+    adapts(HeadSig)
+
+    def __call__(self):
+        op = self.state.encode(self.binding.op)
+        op_length = FormulaCode(LengthSig(), coerce(IntegerDomain()),
+                                self.binding, op=op)
+        zero_literal = LiteralCode(0, coerce(IntegerDomain()), self.binding)
+        one_literal = LiteralCode(1, coerce(IntegerDomain()), self.binding)
+        if self.binding.length is None:
+            length = one_literal
+        else:
+            length = self.state.encode(self.binding.length)
+        if isinstance(length, LiteralCode):
+            if length.value is None:
+                length = one_literal
+            if length.value >= 0:
+                return FormulaCode(SubstringSig(), self.binding.domain,
+                                   self.binding, op=op, start=one_literal,
+                                   length=length)
+        length = FormulaCode(IfNullSig(), length.domain, self.binding,
+                             lop=length, rop=one_literal)
+        negative_length = FormulaCode(AddSig(), length.domain, self.binding,
+                                      lop=op_length, rop=length)
+        if_positive = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                  self.binding, lop=length, rop=zero_literal)
+        if_negative = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                  self.binding, lop=negative_length,
+                                  rop=zero_literal)
+        length = FormulaCode(IfSig(), length.domain, self.binding,
+                             predicates=[if_positive, if_negative],
+                             consequents=[length, negative_length],
+                             alternative=zero_literal)
+        return FormulaCode(SubstringSig(), self.binding.domain, self.binding,
+                           op=op, start=one_literal, length=length)
+
+
+class EncodeTail(EncodeFunction):
+
+    adapts(TailSig)
+
+    def __call__(self):
+        op = self.state.encode(self.binding.op)
+        op_length = FormulaCode(LengthSig(), coerce(IntegerDomain()),
+                                self.binding, op=op)
+        zero_literal = LiteralCode(0, coerce(IntegerDomain()), self.binding)
+        one_literal = LiteralCode(1, coerce(IntegerDomain()), self.binding)
+        if self.binding.length is None:
+            length = one_literal
+        else:
+            length = self.state.encode(self.binding.length)
+        if isinstance(length, LiteralCode):
+            if length.value is None:
+                length = one_literal
+            if length.value < 0:
+                start = length.clone(value=1-length.value)
+                return FormulaCode(SubstringSig(), self.binding.domain,
+                                   self.binding, op=op,
+                                   start=start, length=None)
+        length = FormulaCode(IfNullSig(), length.domain, self.binding,
+                             lop=length, rop=one_literal)
+        start = FormulaCode(SubtractSig(), length.domain, self.binding,
+                            lop=one_literal, rop=length)
+        positive_start = FormulaCode(AddSig(), length.domain, self.binding,
+                                     lop=op_length, rop=start)
+        if_negative = FormulaCode(CompareSig('<'), coerce(BooleanDomain()),
+                                  self.binding, lop=length, rop=zero_literal)
+        if_positive = FormulaCode(CompareSig('<='), coerce(BooleanDomain()),
+                                  self.binding, lop=length, rop=op_length)
+        start = FormulaCode(IfSig(), length.domain, self.binding,
+                            predicates=[if_negative, if_positive],
+                            consequents=[start, positive_start],
+                            alternative=one_literal)
+        return FormulaCode(SubstringSig(), self.binding.domain, self.binding,
+                           op=op, start=start, length=None)
+
+
+class EncodeSlice(EncodeFunction):
+
+    adapts(SliceSig)
+
+    def __call__(self):
+        op = self.state.encode(self.binding.op)
+        op_length = FormulaCode(LengthSig(), coerce(IntegerDomain()),
+                                self.binding, op=op)
+        null_literal = LiteralCode(None, coerce(IntegerDomain()), self.binding)
+        zero_literal = LiteralCode(0, coerce(IntegerDomain()), self.binding)
+        one_literal = LiteralCode(1, coerce(IntegerDomain()), self.binding)
+        if self.binding.left is None:
+            left = zero_literal
+        else:
+            left = self.state.encode(self.binding.left)
+        if self.binding.right is None:
+            right = null_literal
+        else:
+            right = self.state.encode(self.binding.right)
+        if isinstance(left, LiteralCode) and left.value is None:
+            start = one_literal
+        elif isinstance(left, LiteralCode) and left.value >= 0:
+            start = left.clone(value=left.value+1)
+        else:
+            left = FormulaCode(IfNullSig(), left.domain, self.binding,
+                               lop=left, rop=zero_literal)
+            start = left
+            negative_start = FormulaCode(AddSig(), left.domain, self.binding,
+                                         lop=left, rop=op_length)
+            if_positive = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                      self.binding, lop=start,
+                                      rop=zero_literal)
+            if_negative = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                      self.binding, lop=negative_start,
+                                      rop=zero_literal)
+            start = FormulaCode(IfSig(), left.domain, self.binding,
+                                predicates=[if_positive, if_negative],
+                                consequents=[start, negative_start],
+                                alternative=zero_literal)
+            start = FormulaCode(AddSig(), left.domain, self.binding,
+                                lop=start, rop=one_literal)
+        if isinstance(right, LiteralCode) and right.value is None:
+            return FormulaCode(SubstringSig(), self.binding.domain,
+                               self.binding, op=op,
+                               start=start, length=None)
+        elif isinstance(right, LiteralCode) and right.value >= 0:
+            if isinstance(start, LiteralCode):
+                assert start.value >= 0
+                value = right.value-start.value+1
+                if value < 0:
+                    value = 0
+                length = right.clone(value=value)
+                return FormulaCode(SubstringSig(), self.binding.domain,
+                                   self.binding, op=op,
+                                   start=start, length=length)
+            end = right.clone(value=right.value+1)
+        else:
+            if not isinstance(right, LiteralCode):
+                right = FormulaCode(IfNullSig(), right.domain, self.binding,
+                                    lop=right, rop=op_length)
+            end = right
+            negative_end = FormulaCode(AddSig(), right.domain, self.binding,
+                                       lop=right, rop=op_length)
+            if_positive = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                      self.binding, lop=end,
+                                      rop=zero_literal)
+            if_negative = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                      self.binding, lop=negative_end,
+                                      rop=zero_literal)
+            end = FormulaCode(IfSig(), right.domain, self.binding,
+                                predicates=[if_positive, if_negative],
+                                consequents=[end, negative_end],
+                                alternative=zero_literal)
+            end = FormulaCode(AddSig(), right.domain, self.binding,
+                                lop=end, rop=one_literal)
+        length = FormulaCode(SubtractSig(), coerce(IntegerDomain()),
+                             self.binding, lop=end, rop=start)
+        condition = FormulaCode(CompareSig('<'), coerce(BooleanDomain()),
+                                self.binding, lop=start, rop=end)
+        length = FormulaCode(IfSig(), length.domain, self.binding,
+                             predicates=[condition],
+                             consequents=[length],
+                             alternative=zero_literal)
+        return FormulaCode(SubstringSig(), self.binding.domain, self.binding,
+                           op=op, start=start, length=length)
+
+
+class EncodeAt(EncodeFunction):
+
+    adapts(AtSig)
+
+    def __call__(self):
+        op = self.state.encode(self.binding.op)
+        op_length = FormulaCode(LengthSig(), coerce(IntegerDomain()),
+                                self.binding, op=op)
+        zero_literal = LiteralCode(0, coerce(IntegerDomain()), self.binding)
+        one_literal = LiteralCode(1, coerce(IntegerDomain()), self.binding)
+        index = self.state.encode(self.binding.index)
+        if self.binding.length is not None:
+            length = self.state.encode(self.binding.length)
+        else:
+            length = one_literal
+        if isinstance(index, LiteralCode) and index.value is None:
+            return FormulaCode(SubstringSig(), self.binding.domain,
+                               self.binding, op=op, start=index,
+                               length=zero_literal)
+        if isinstance(length, LiteralCode) and length.value is None:
+            length = one_literal
+        if (isinstance(index, LiteralCode) and index.value >= 0
+                and isinstance(length, LiteralCode)):
+            index_value = index.value
+            length_value = length.value
+            if length_value < 0:
+                index_value += length_value
+                length_value = -length_value
+            if index_value < 0:
+                length_value += index_value
+                index_value = 0
+            if length_value < 0:
+                length_value = 0
+            start = index.clone(value=index_value+1)
+            length = length.clone(value=length_value)
+            return FormulaCode(SubstringSig(), self.binding.domain,
+                               self.binding, op=op, start=start, length=length)
+        length = FormulaCode(IfNullSig(), length.domain, self.binding,
+                             lop=length, rop=one_literal)
+        negative_index = FormulaCode(AddSig(), index.domain, self.binding,
+                                     lop=index, rop=op_length)
+        condition = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                self.binding, lop=index, rop=zero_literal)
+        index = FormulaCode(IfSig(), index.domain, self.binding,
+                            predicates=[condition], consequents=[index],
+                            alternative=negative_index)
+        condition = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                self.binding, lop=length, rop=zero_literal)
+        negative_index = FormulaCode(AddSig(), index.domain, self.binding,
+                                     lop=index, rop=length)
+        negative_length = FormulaCode(ReversePolaritySig(), length.domain,
+                                      self.binding, op=length)
+        index = FormulaCode(IfSig(), index.domain, self.binding,
+                            predicates=[condition], consequents=[index],
+                            alternative=negative_index)
+        length = FormulaCode(IfSig(), length.domain, self.binding,
+                             predicates=[condition], consequents=[length],
+                             alternative=negative_length)
+        condition = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                self.binding, lop=index, rop=zero_literal)
+        negative_length = FormulaCode(AddSig(), length.domain, self.binding,
+                                      lop=length, rop=index)
+        index = FormulaCode(IfSig(), index.domain, self.binding,
+                            predicates=[condition], consequents=[index],
+                            alternative=zero_literal)
+        length = FormulaCode(IfSig(), length.domain, self.binding,
+                             predicates=[condition], consequents=[length],
+                             alternative=negative_length)
+        condition = FormulaCode(CompareSig('>='), coerce(BooleanDomain()),
+                                self.binding, lop=length, rop=zero_literal)
+        length = FormulaCode(IfSig(), length.domain, self.binding,
+                             predicates=[condition], consequents=[length],
+                             alternative=zero_literal)
+        start = FormulaCode(AddSig(), index.domain, self.binding,
+                            lop=index, rop=one_literal)
+        return FormulaCode(SubstringSig(), self.binding.domain,
+                           self.binding, op=op, start=start, length=length)
+
+
+class EncodeReplace(EncodeFunction):
+
+    adapts(ReplaceSig)
+
+    def __call__(self):
+        op = self.state.encode(self.binding.op)
+        old = self.state.encode(self.binding.old)
+        new = self.state.encode(self.binding.new)
+        empty = LiteralCode('', old.domain, self.binding)
+        old = FormulaCode(IfNullSig(), old.domain, self.binding,
+                          lop=old, rop=empty)
+        return FormulaCode(self.signature, self.domain, self.binding,
+                           op=op, old=old, new=new)
+
+
+class EncodeAggregate(EncodeFunction):
 
     adapts(AggregateSig)
 
         return self.unit
 
 
-class EncodeCount(EncodeBySignature):
+class EncodeCount(EncodeFunction):
 
     adapts(CountSig)
 

src/htsql/tr/fn/signature.py

     pass
 
 
-class DirectSig(Signature):
-
-    slots = [
-            Slot('table'),
-    ]
-
-
 class FiberSig(Signature):
 
     slots = [
             Slot('table'),
-            Slot('image'),
+            Slot('image', is_mandatory=False),
             Slot('counterimage', is_mandatory=False),
     ]
 
     ]
 
 
-class DateSig(Signature):
+class MakeDateSig(Signature):
 
     slots = [
             Slot('year'),
     ]
 
 
+class ExtractSig(UnarySig):
+    pass
+
+
+class ExtractYearSig(ExtractSig):
+    pass
+
+
+class ExtractMonthSig(ExtractSig):
+    pass
+
+
+class ExtractDaySig(ExtractSig):
+    pass
+
+
 class AddSig(BinarySig):
     pass
 
     pass
 
 
+class LikeSig(BinarySig, PolarSig):
+    pass
+
+
+class ReplaceSig(Signature):
+
+    slots = [
+            Slot('op'),
+            Slot('old'),
+            Slot('new'),
+    ]
+
+
+class SubstringSig(Signature):
+
+    slots = [
+            Slot('op'),
+            Slot('start'),
+            Slot('length', is_mandatory=False),
+    ]
+
+
+class HeadSig(Signature):
+
+    slots = [
+            Slot('op'),
+            Slot('length', is_mandatory=False),
+    ]
+
+
+class TailSig(Signature):
+
+    slots = [
+            Slot('op'),
+            Slot('length', is_mandatory=False),
+    ]
+
+
+class SliceSig(Signature):
+
+    slots = [
+            Slot('op'),
+            Slot('left', is_mandatory=False),
+            Slot('right', is_mandatory=False),
+    ]
+
+
+class AtSig(Signature):
+
+    slots = [
+            Slot('op'),
+            Slot('index'),
+            Slot('length', is_mandatory=False),
+    ]
+
+
+class UpperSig(UnarySig):
+    pass
+
+
+class LowerSig(UnarySig):
+    pass
+
+
+class TrimSig(UnarySig):
+
+    def __init__(self, is_left=True, is_right=True):
+        assert isinstance(is_left, bool)
+        assert isinstance(is_right, bool)
+        assert is_left or is_right
+        super(TrimSig, self).__init__(equality_vector=(is_left, is_right))
+        self.is_left = is_left
+        self.is_right = is_right
+
+
+class TodaySig(NullarySig):
+    pass
+
+
 class AggregateSig(Signature):
 
     slots = [

src/htsql_pgsql/tr/dump.py

 from htsql.tr.dump import (FormatLiteral, DumpBranch, DumpInteger, DumpFloat,
                            DumpDecimal, DumpDate, DumpToDecimal, DumpToFloat,
                            DumpToString)
-from htsql.tr.fn.signature import (DateSig, ContainsSig, DateIncrementSig,
+from htsql.tr.fn.signature import (MakeDateSig, ContainsSig, DateIncrementSig,
                                    DateDecrementSig, DateDifferenceSig)
-from htsql.tr.fn.dump import DumpFunction
+from htsql.tr.fn.dump import (DumpFunction, DumpLike, DumpDateIncrement,
+                              DumpDateDecrement, DumpDateDifference,
+                              DumpMakeDate, DumpExtractYear, DumpExtractMonth,
+                              DumpExtractDay)
 from htsql.tr.frame import LiteralPhrase, NullPhrase
 from htsql.tr.error import SerializeError
 
         self.format("CAST({base} AS TEXT)", base=self.base)
 
 
-class PGSQLDumpDateConstructor(DumpFunction):
+class PGSQLDumpMakeDate(DumpMakeDate):
 
-    adapts(DateSig)
     template = ("CAST('0001-01-01'::DATE"
                 " + ({year} - 1) * '1 YEAR'::INTERVAL"
                 " + ({month} - 1) * '1 MONTH'::INTERVAL"
                 " AS DATE)")
 
 
-class PGSQLDumpDateIncrement(DumpFunction):
+class PGSQLDumpDateIncrement(DumpDateIncrement):
 
-    adapts(DateIncrementSig)
     template = "({lop} + {rop})"
 
 
-class PGSQLDumpDateDecrement(DumpFunction):
+class PGSQLDumpDateDecrement(DumpDateDecrement):
 
-    adapts(DateDecrementSig)
     template = "({lop} - {rop})"
 
 
-class PGSQLDumpDateDifference(DumpFunction):
+class PGSQLDumpDateDifference(DumpDateDifference):
 
-    adapts(DateDifferenceSig)
     template = "({lop} - {rop})"
 
 
-class PGSQLDumpStringContains(DumpFunction):
+class PGSQLDumpExtractYear(DumpExtractYear):
 
-    adapts(ContainsSig)
+    template = "CAST(EXTRACT(YEAR FROM {op}) AS INTEGER)"
+
+
+class PGSQLDumpExtractMonth(DumpExtractMonth):
+
+    template = "CAST(EXTRACT(MONTH FROM {op}) AS INTEGER)"
+
+
+class PGSQLDumpExtractDay(DumpExtractDay):
+
+    template = "CAST(EXTRACT(DAY FROM {op}) AS INTEGER)"
+
+
+class PGSQLDumpLike(DumpLike):
 
     def __call__(self):
-        if isinstance(self.phrase.rop, NullPhrase):
-            self.format("({lop} {polarity:not}ILIKE {rop})",
-                        self.phrase, self.phrase.signature)
-        elif isinstance(self.phrase.rop, LiteralPhrase):
-            value = self.phrase.rop.value
-            value.replace("%", "\\%").replace("_", "\\_")
-            value = "%"+value+"%&quo