Kirill Simonov avatar Kirill Simonov committed ba3d93f

Partially implemented aggregate translation; added function `count()`.

Also added the concatenation operator, and a lot of translator fixes.

Comments (0)

Files changed (11)

src/htsql/fmt/text.py

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

src/htsql/tr/assembler.py

 
 from ..adapter import Adapter, adapts, find_adapters
 from .code import (Code, Space, ScalarSpace, FreeTableSpace, JoinedTableSpace,
-                   ScreenSpace, OrderedSpace, Expression, Unit,
+                   ScreenSpace, OrderedSpace, RelativeSpace, Expression, Unit,
                    ColumnUnit, AggregateUnit, SegmentCode, QueryCode)
 from .term import (TableTerm, ScalarTerm, FilterTerm, JoinTerm,
                    CorrelationTerm, ProjectionTerm, OrderingTerm, WrapperTerm,
         routes[self.space] = [RIGHT]
         routes[backbone] = [RIGHT]
         tie = SeriesTie(self.space)
-        return JoinTerm(left, right, tie, True, self.space, baseline,
+        return JoinTerm(left, right, [tie], True, self.space, baseline,
                         routes, self.space.mark)
 
     def inject(self, term):
     def inject(self, term):
         assert term.space.spans(self.code.space)
         assert not term.space.spans(self.code.plural_space)
-        assert not self.code.space.span(self.code.plural_space)
+        assert not self.code.space.spans(self.code.plural_space)
         assert self.code.plural_space.spans(self.code.space)
         with_base_term = (not self.code.space.dominates(term.space))
         if with_base_term:
             baseline = baseline.parent
         baseline = baseline.axes()
         plural_term = self.assembler.assemble(plural_space, baseline)
+        plural_term = self.assembler.inject(self.code.expression, plural_term)
         ties = []
+        axes = []
         if (base_backbone.concludes(baseline)
                 or baseline.parent in plural_term.routes):
             axis = baseline
             while axis in plural_term.routes:
+                axes.append(axis)
                 tie = ParallelTie(axis)
                 ties.append(tie)
                 axis = axis.parent
         else:
-            tie = SeriesTie()
-
+            axes.append(baseline)
+            tie = SeriesTie(baseline)
+            ties.append(tie)
+        relative_space = RelativeSpace(baseline.parent,
+                                       plural_space, self.code.mark)
+        routes = {}
+        for axis in axes:
+            routes[axis] = [FORWARD] + plural_term.routes[axis]
+        routes[self.code] = []
+        projected_term = ProjectionTerm(plural_term, ties, relative_space,
+                                        baseline.parent, routes,
+                                        self.code.mark)
+        if with_base_term:
+            assert False
+        if (base_backbone.concludes(baseline)
+                or baseline.parent in plural_term.routes):
+            for axis in axes:
+                term = self.assembler.inject(axis, term)
+        else:
+            for axis in axes:
+                term = self.assembler.inject(axis.parent, term)
+        left = term
+        right = projected_term
+        routes = {}
+        for key in left.routes:
+            routes[key] = [LEFT] + left.routes[key]
+        routes[self.code] = [RIGHT]
+        return JoinTerm(left, right, ties, False,
+                        left.space, left.baseline, routes, left.mark)
 
 
 class AssembleSegment(Assemble):

src/htsql/tr/code.py

                 self.filter == other.filter)
 
 
+class RelativeSpace(Space):
+
+    def __init__(self, parent, filter, mark):
+        assert isinstance(filter, Space)
+        super(RelativeSpace, self).__init__(parent, parent.table,
+                                            True, False, mark,
+                                            hash=(self.__class__,
+                                                  parent.hash,
+                                                  filter.hash))
+        self.filter = filter
+
+    def resembles(self, other):
+        return (isinstance(other, RelativeSpace) and
+                self.filter == other.filter)
+
+
 class OrderedSpace(Space):
 
     def __init__(self, parent, order, limit, offset, mark):
             arguments_hash.append((key, value))
         hash = (self.__class__, tuple(arguments_hash))
         super(FunctionExpression, self).__init__(domain, mark, hash=hash)
+        self.arguments = arguments
+        for key in arguments:
+            setattr(self, key, arguments[key])
+
+    def get_units(self):
+        units = []
+        for key in sorted(self.arguments):
+            value = self.arguments[key]
+            if isinstance(value, list):
+                for item in value:
+                    units.extend(item.get_units())
+            elif value is not None:
+                units.extend(value.get_units())
+        return units
 
 
 class Unit(Expression):

src/htsql/tr/compiler.py

                 supplies[demand] = phrase_by_column[column]
             elif appointment.is_frame:
                 supplies[demand] = frame
+            else:
+                assert False
         return supplies
 
 
         for demand in demands:
             if demand.sketch not in self.sketch.absorbed:
                 inner_demands.append(demand)
-            inner_demands.extend(demand.appointment.get_demands())
+            else:
+                inner_demands.extend(demand.get_demands())
         for appointment in self.sketch.select:
             inner_demands.extend(appointment.get_demands())
         for attachment in self.sketch.linkage:
         for appointment, dir in self.sketch.order:
             inner_demands.extend(appointment.get_demands())
 
+        idx = 0
+        while idx < len(inner_demands):
+            demand = inner_demands[idx]
+            if demand.sketch in self.sketch.absorbed:
+                inner_demands.extend(demand.get_demands())
+            idx += 1
+
         demands_by_child = {}
         demands_by_child[None] = []
         for attachment in self.sketch.linkage:
             child = attachment.sketch
             child_demands = demands_by_child[child]
             child_supplies = self.compiler.compile(child, child_demands)
-            assert len(child_demands) == len(child_supplies), (child_demands, child_supplies)
             inner_supplies.update(child_supplies)
 
         branch_demands = reversed(demands_by_child[None])
         if len(conditions) == 1:
             filter = conditions[0]
         elif len(conditions) > 1:
-            filter = Conjunction(conditions, self.sketch.mark)
+            filter = ConjunctionPhrase(conditions, self.sketch.mark)
 
         group = []
         for appointment in self.sketch.group:
         if len(conditions) == 1:
             group_filter = conditions[0]
         elif len(conditions) > 1:
-            group_filter = Conjunction(conditions, self.sketch.mark)
+            group_filter = ConjunctionPhrase(conditions, self.sketch.mark)
 
         order = []
         for appointment, dir in self.sketch.order:
         if len(conditions) == 1:
             condition = conditions[0]
         elif len(conditions) > 1:
-            condition = Conjunction(conditions, attachment.sketch.mark)
+            condition = ConjunctionPhrase(conditions, attachment.sketch.mark)
         is_inner = attachment.sketch.is_inner
         return Link(frame, condition, is_inner)
 
         self.compiler = compiler
 
     def evaluate(self, references):
-        raise NotImplementedError()
+        raise NotImplementedError(self.expression)
 
 
 class EvaluateLiteral(Evaluate):

src/htsql/tr/encoder.py

     adapts(JoinedTableBinding, Encoder)
 
     def relate(self):
-        return self.encoder.relate(self.binding.parent)
+        space = self.encoder.relate(self.binding.parent)
+        for join in self.binding.joins:
+            space = JoinedTableSpace(space, join, self.binding.mark)
+        return space
 
 
 class EncodeColumn(Encode):
     adapts(ColumnBinding, Encoder)
 
     def relate(self):
-        space = self.encoder.relate(self.binding.parent)
-        lookup = Lookup(self.binding)
-        binding = lookup.as_table()
-        for join in binding.joins:
-            space = JoinedTableSpace(space, join, binding.mark)
-        return space
+        return self.encoder.relate(self.binding.parent)
 
     def encode(self):
         space = self.encoder.relate(self.binding.parent)

src/htsql/tr/fn/function.py

 from ..binding import (LiteralBinding, FunctionBinding,
                        EqualityBinding, InequalityBinding,
                        ConjunctionBinding, DisjunctionBinding, NegationBinding)
+from ..encoder import Encoder, Encode
+from ..code import FunctionExpression, AggregateUnit
+from ..compiler import Compiler, Evaluate
+from ..frame import FunctionPhrase
+from ..serializer import Serializer, Format, Serialize
 
 
 class named(str):
     adapts(named['_=_'])
 
     parameters = [
-            Parameter('left_argument'),
-            Parameter('right_argument'),
+            Parameter('left'),
+            Parameter('right'),
     ]
 
-    def correlate(self, left_argument, right_argument, syntax, parent):
-        domain = self.binder.coerce(left_argument.domain,
-                                    right_argument.domain)
+    def correlate(self, left, right, syntax, parent):
+        domain = self.binder.coerce(left.domain,
+                                    right.domain)
         if domain is None:
             raise InvalidArgumentError("invalid arguments", syntax.mark)
         domain = self.binder.coerce(domain)
         if domain is None:
             raise InvalidArgumentError("invalid arguments", syntax.mark)
-        left_argument = self.binder.cast(left_argument, domain)
-        right_argument = self.binder.cast(right_argument, domain)
-        yield EqualityBinding(parent, left_argument, right_argument, syntax)
+        left = self.binder.cast(left, domain)
+        right = self.binder.cast(right, domain)
+        yield EqualityBinding(parent, left, right, syntax)
 
 
 class InequalityOperator(ProperFunction):
     adapts(named['_!=_'])
 
     parameters = [
-            Parameter('left_argument'),
-            Parameter('right_argument'),
+            Parameter('left'),
+            Parameter('right'),
     ]
 
-    def correlate(self, left_argument, right_argument, syntax, parent):
-        domain = self.binder.coerce(left_argument.domain,
-                                    right_argument.domain)
+    def correlate(self, left, right, syntax, parent):
+        domain = self.binder.coerce(left.domain,
+                                    right.domain)
         if domain is None:
             raise InvalidArgumentError("invalid arguments", syntax.mark)
         domain = self.binder.coerce(domain)
         if domain is None:
             raise InvalidArgumentError("invalid arguments", syntax.mark)
-        left_argument = self.binder.cast(left_argument, domain)
-        right_argument = self.binder.cast(right_argument, domain)
-        yield InequalityBinding(parent, left_argument, right_argument, syntax)
+        left = self.binder.cast(left, domain)
+        right = self.binder.cast(right, domain)
+        yield InequalityBinding(parent, left, right, syntax)
 
 
 class ConjunctionOperator(ProperFunction):
     adapts(named['_&_'])
 
     parameters = [
-            Parameter('left_argument'),
-            Parameter('right_argument'),
+            Parameter('left'),
+            Parameter('right'),
     ]
 
-    def correlate(self, left_argument, right_argument, syntax, parent):
-        left_argument = self.binder.cast(left_argument, BooleanDomain())
-        right_argument = self.binder.cast(right_argument, BooleanDomain())
-        yield ConjunctionBinding(parent,
-                                 [left_argument, right_argument], syntax)
+    def correlate(self, left, right, syntax, parent):
+        left = self.binder.cast(left, BooleanDomain())
+        right = self.binder.cast(right, BooleanDomain())
+        yield ConjunctionBinding(parent, [left, right], syntax)
 
 
 class DisjunctionOperator(ProperFunction):
     adapts(named['_|_'])
 
     parameters = [
-            Parameter('left_argument'),
-            Parameter('right_argument'),
+            Parameter('left'),
+            Parameter('right'),
     ]
 
-    def correlate(self, left_argument, right_argument, syntax, parent):
-        left_argument = self.binder.cast(left_argument, BooleanDomain())
-        right_argument = self.binder.cast(right_argument, BooleanDomain())
-        yield DisjunctionBinding(parent,
-                                 [left_argument, right_argument], syntax)
+    def correlate(self, left, right, syntax, parent):
+        left = self.binder.cast(left, BooleanDomain())
+        right = self.binder.cast(right, BooleanDomain())
+        yield DisjunctionBinding(parent, [left, right], syntax)
 
 
 class AdditionOperator(ProperFunction):
     adapts(named['_+_'])
 
     parameters = [
-            Parameter('left_argument'),
-            Parameter('right_argument'),
+            Parameter('left'),
+            Parameter('right'),
     ]
 
-    def correlate(self, left_argument, right_argument, syntax, parent):
-        Implementation = Add.realize(left_argument.domain,
-                                     right_argument.domain)
-        addition = Implementation(left_argument, right_argument,
-                                  self.binder, syntax, parent)
+    def correlate(self, left, right, syntax, parent):
+        Implementation = Add.realize(left.domain, right.domain)
+        addition = Implementation(left, right, self.binder, syntax, parent)
         yield addition()
 
 
 
     adapts(Domain, Domain)
 
-    def __init__(self, left_argument, right_argument, binder, syntax, parent):
-        self.left_argument = left_argument
-        self.right_argument = right_argument
+    def __init__(self, left, right, binder, syntax, parent):
+        self.left = left
+        self.right = right
         self.binder = binder
         self.syntax = syntax
         self.parent = parent
                                    self.syntax.mark)
 
 
-class ConcatenateBinding(FunctionBinding):
+class ConcatenationBinding(FunctionBinding):
 
-    def __init__(self, parent, left_argument, right_argument, syntax):
-        super(ConcatenateBinding, self).__init__(parent, StringDomain(), syntax,
-                                                 left_argument=left_argument,
-                                                 right_argument=right_argument)
+    def __init__(self, parent, left, right, syntax):
+        super(ConcatenationBinding, self).__init__(parent, StringDomain(),
+                                                   syntax,
+                                                   left=left, right=right)
+
+
+class ConcatenationExpression(FunctionExpression):
+
+    def __init__(self, left, right, mark):
+        super(ConcatenationExpression, self).__init__(StringDomain(), mark,
+                                                      left=left, right=right)
+
+
+class ConcatenationPhrase(FunctionPhrase):
+
+    def __init__(self, left, right, mark):
+        super(ConcatenationPhrase, self).__init__(StringDomain(), False, mark,
+                                                  left=left, right=right)
 
 
 class Concatenate(Add):
     adapts_none()
 
     def __call__(self):
-        left_argument = self.binder.cast(self.left_argument, StringDomain(),
-                                         parent=self.parent)
-        right_argument = self.binder.cast(self.right_argument, StringDomain(),
-                                          parent=self.parent)
-        return ConcatenateBinding(self.parent, left_argument, right_argument,
-                                  self.syntax)
+        left = self.binder.cast(self.left, StringDomain(),
+                                parent=self.parent)
+        right = self.binder.cast(self.right, StringDomain(),
+                                 parent=self.parent)
+        return ConcatenationBinding(self.parent, left, right, self.syntax)
 
 
 class ConcatenateStringToString(Concatenate):
     adapts(UntypedDomain, UntypedDomain)
 
 
+class EncodeConcatenation(Encode):
+
+    adapts(ConcatenationBinding, Encoder)
+
+    def encode(self):
+        left = self.encoder.encode(self.binding.left)
+        right = self.encoder.encode(self.binding.right)
+        return ConcatenationExpression(left, right, self.binding.mark)
+
+
+class EvaluateConcatenation(Evaluate):
+
+    adapts(ConcatenationExpression, Compiler)
+
+    def evaluate(self, references):
+        left = self.compiler.evaluate(self.expression.left, references)
+        right = self.compiler.evaluate(self.expression.right, references)
+        return ConcatenationPhrase(left, right, self.expression.mark)
+
+
+class FormatFunctions(Format):
+
+    def concat_op(self, left, right):
+        return "(%s || %s)" % (left, right)
+
+    def count_fn(self, condition):
+        return "COUNT(NULLIF(%s, FALSE))" % condition
+
+    def count_wrapper(self, aggregate):
+        return "COALESCE(%s, 0)" % aggregate
+
+
+class SerializeConcatenation(Serialize):
+
+    adapts(ConcatenationPhrase, Serializer)
+
+    def serialize(self):
+        left = self.serializer.serialize(self.phrase.left)
+        right = self.serializer.serialize(self.phrase.right)
+        return self.format.concat_op(left, right)
+
+
+class CountFunction(ProperFunction):
+
+    adapts(named['count'])
+
+    parameters = [
+            Parameter('condition'),
+    ]
+
+    def correlate(self, condition, syntax, parent):
+        condition = self.binder.cast(condition, BooleanDomain())
+        yield CountBinding(parent, condition, syntax)
+
+
+class CountBinding(FunctionBinding):
+
+    def __init__(self, parent, condition, syntax):
+        super(CountBinding, self).__init__(parent, IntegerDomain(), syntax,
+                                           condition=condition)
+
+
+class CountExpression(FunctionExpression):
+
+    def __init__(self, condition, mark):
+        super(CountExpression, self).__init__(IntegerDomain(), mark,
+                                              condition=condition)
+
+class CountWrapperExpression(FunctionExpression):
+
+    def __init__(self, aggregate, mark):
+        super(CountWrapperExpression, self).__init__(IntegerDomain(), mark,
+                                                     aggregate=aggregate)
+
+
+class CountPhrase(FunctionPhrase):
+
+    def __init__(self, condition, mark):
+        super(CountPhrase, self).__init__(IntegerDomain(), True, mark,
+                                          condition=condition)
+
+
+class CountWrapperPhrase(FunctionPhrase):
+
+    def __init__(self, aggregate, mark):
+        super(CountWrapperPhrase, self).__init__(IntegerDomain(), False, mark,
+                                                 aggregate=aggregate)
+
+
+class EncodeCount(Encode):
+
+    adapts(CountBinding, Encoder)
+
+    def encode(self):
+        condition = self.encoder.encode(self.binding.condition)
+        function = CountExpression(condition, self.binding.mark)
+        space = self.encoder.relate(self.binding.parent)
+        plural_units = [unit for unit in condition.get_units()
+                             if not space.spans(unit.space)]
+        if not plural_units:
+            raise InvalidArgumentError("a plural expression is required",
+                                       condition.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 InvalidArgumentError("invalid plural expression",
+                                       condition.mark)
+        plural_space = plural_spaces[0]
+        if not plural_space.spans(space):
+            raise InvalidArgumentError("invalid plural expression",
+                                       condition.mark)
+        aggregate = AggregateUnit(function, plural_space, space, function.mark)
+        wrapper = CountWrapperExpression(aggregate, aggregate.mark)
+        return wrapper
+
+
+class EvaluateCount(Evaluate):
+
+    adapts(CountExpression, Compiler)
+
+    def evaluate(self, references):
+        condition = self.compiler.evaluate(self.expression.condition,
+                                           references)
+        return CountPhrase(condition, self.expression.mark)
+
+
+class EvaluateCountWrapper(Evaluate):
+
+    adapts(CountWrapperExpression, Compiler)
+
+    def evaluate(self, references):
+        aggregate = self.compiler.evaluate(self.expression.aggregate,
+                                           references)
+        return CountWrapperPhrase(aggregate, self.expression.mark)
+
+
+class SerializeCount(Serialize):
+
+    adapts(CountPhrase, Serializer)
+
+    def serialize(self):
+        condition = self.serializer.serialize(self.phrase.condition)
+        return self.format.count_fn(condition)
+
+
+class SerializeCountWrapper(Serialize):
+
+    adapts(CountWrapperPhrase, Serializer)
+
+    def serialize(self):
+        aggregate = self.serializer.serialize(self.phrase.aggregate)
+        return self.format.count_wrapper(aggregate)
+
+
 function_adapters = find_adapters()
 
 

src/htsql/tr/frame.py

         self.value = value
 
 
+class FunctionPhrase(Phrase):
+
+    def __init__(self, domain, is_nullable, mark, **arguments):
+        super(FunctionPhrase, self).__init__(domain, is_nullable, mark)
+        self.arguments = arguments
+        for key in arguments:
+            setattr(self, key, arguments[key])
+
+
 class LeafReferencePhrase(Phrase):
 
     def __init__(self, frame, is_inner, column, mark):

src/htsql/tr/outliner.py

         outline = Outline(sketch, self)
         return outline.outline(*args, **kwds)
 
-    def delegate(self, unit, sketch, routes):
+    def delegate(self, unit, sketch, term):
         delegate = Delegate(unit, self)
-        return delegate.delegate(sketch, routes)
+        return delegate.delegate(sketch, term)
 
-    def appoint(self, expression, sketch, routes):
+    def appoint(self, expression, sketch, term):
         demand_by_unit = {}
         for unit in expression.get_units():
-            demand = self.delegate(unit, sketch, routes)
+            demand = self.delegate(unit, sketch, term)
             demand_by_unit[unit] = demand
         return BranchAppointment(expression, demand_by_unit)
 
         attachment = Attachment(child)
         linkage = [attachment]
         appointment = self.outliner.appoint(self.term.filter, child,
-                                            self.term.child.routes)
+                                            self.term.child)
         filter = [appointment]
         return BranchSketch(linkage=linkage,
                             filter=filter,
         for tie in self.term.ties:
             for left_unit, right_unit in self.outliner.connect(tie):
                 left_appointment = self.outliner.appoint(left_unit,
-                                    left_child, self.term.left_child.routes)
+                                    left_child, self.term.left_child)
                 right_appointment = self.outliner.appoint(right_unit,
-                                    right_child, self.term.right_child.routes)
+                                    right_child, self.term.right_child)
                 connection = Connection(left_appointment, right_appointment)
                 connections.append(connection)
         right_attachment = Attachment(right_child, connections)
         for tie in self.term.ties:
             for left_code, right_code in self.outliner.connect(tie):
                 left_appointment = self.outliner.appoint(left_code,
-                                    left_child, self.term.left_child.routes)
+                                    left_child, self.term.left_child)
                 right_appointment = self.outliner.appoint(right_code,
-                                    right_child, self.term.right_child.routes)
+                                    right_child, self.term.right_child)
                 connection = Connection(left_appointment, right_appointment)
                 connections.append(connection)
         right_attachment = Attachment(right_child, connections)
         for tie in self.term.ties:
             for left_code, right_code in self.outliner.connect(tie):
                 appointment = self.outliner.appoint(right_code, child,
-                                                    self.term.child.routes)
+                                                    self.term.child)
                 group.append(appointment)
         return BranchSketch(linkage=linkage,
                             group=group,
         linkage = [attachment]
         order = []
         for code, dir in self.term.order:
-            appointment = self.outliner.appoint(code, child,
-                                                self.term.child.routes)
+            appointment = self.outliner.appoint(code, child, self.term.child)
             order.append((appointment, dir))
         return BranchSketch(linkage=linkage,
                             order=order,
         linkage = [attachment]
         select = []
         for code in self.term.select:
-            appointment = self.outliner.appoint(code, child,
-                                                self.term.child.routes)
+            appointment = self.outliner.appoint(code, child, self.term.child)
             select.append(appointment)
         return SegmentSketch(select=select,
                              linkage=linkage,
 
     adapts(ColumnUnit, Outliner)
 
-    def delegate(self, sketch, routes):
-        route = routes[self.unit.space]
+    def delegate(self, sketch, term):
+        route = term.routes[self.unit.space]
         for idx in route:
             sketch = sketch.linkage[idx].sketch
         appointment = LeafAppointment(self.unit.column,
 
     adapts(AggregateUnit, Outliner)
 
+    def delegate(self, sketch, term):
+        route = term.routes[self.unit]
+        for idx in route:
+            sketch = sketch.linkage[idx].sketch
+            term = term.children[idx]
+        appointment = self.outliner.appoint(self.unit.expression,
+                                            sketch.linkage[0].sketch,
+                                            term.children[0])
+        return Demand(sketch, appointment)
+
 
 class Flatten(Adapter):
 
             linkage.append(attachment)
         replaced = self.sketch.replaced[:]
         replaced.append(self.sketch)
+        if (len(linkage) > 1 and linkage[0].sketch.is_scalar
+                             and linkage[1].sketch.is_inner):
+            linkage = linkage[1:]
         sketch = self.sketch
         if linkage != self.sketch.linkage:
             sketch = self.sketch.clone(linkage=linkage, replaced=replaced)
         if child.select or child.group or child.group_filter:
             return sketch
         if ((child.order or child.limit is not None or
-            child.offset is not None) and (len(sketch.linkage) > 1 or
-                sketch.order or sketch.limit is not None or
+            child.offset is not None) and (sketch.order or
+                sketch.limit is not None or
                 sketch.offset is not None)):
             return sketch
         select = sketch.select

src/htsql/tr/serializer.py

         child = self.format.name(self.phrase.column.name)
         return self.format.attr(parent, child)
 
+    def call(self):
+        return self.phrase.column.name
+
 
 class SerializeBranchReference(SerializePhrase):
 
         child = self.format.name(child)
         return self.format.attr(parent, child)
 
+    def call(self):
+        return self.serializer.call(self.phrase.phrase)
+
 
 class SerializeCorrelatedFrame(SerializePhrase):
 

src/htsql/tr/sketch.py

         for unit in self.expression.get_units():
             demand = self.demand_by_unit[unit]
             yield demand
-            for demand in demand.appointment.get_demands():
-                yield demand
 
 
 class FrameAppointment(Appointment):

src/htsql/tr/term.py

 class ProjectionTerm(UnaryTerm):
 
     def __init__(self, child, ties, space, baseline, routes, mark):
-        assert isinstance(ties, listof(Ties))
-        super(ProjectionTerm).__init__(child, space, baseline, routes, mark)
+        assert isinstance(ties, listof(Tie))
+        super(ProjectionTerm, self).__init__(child, space, baseline,
+                                             routes, mark)
         self.ties = ties
 
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.