Commits

Kirill Simonov committed bfc93dd

top(): added binding and flow nodes.

Comments (0)

Files changed (7)

src/htsql/core/tr/binding.py

         self.images = images
 
 
+class ClipBinding(ScopingBinding):
+
+    def __init__(self, base, seed, limit, offset, syntax):
+        assert isinstance(seed, Binding)
+        assert isinstance(limit, maybe(int))
+        assert isinstance(offset, maybe(int))
+        super(ClipBinding, self).__init__(base, seed.domain, syntax)
+        self.seed = seed
+        self.limit = limit
+        self.offset = offset
+
+
 class SieveBinding(ChainingBinding):
     """
     Represents a sieve expression.

src/htsql/core/tr/encode.py

                       SelectionBinding, HomeBinding, RootBinding,
                       FreeTableBinding, AttachedTableBinding, ColumnBinding,
                       QuotientBinding, KernelBinding, ComplementBinding,
-                      CoverBinding, ForkBinding, LinkBinding, SieveBinding,
-                      SortBinding, CastBinding, RescopingBinding,
+                      CoverBinding, ForkBinding, LinkBinding, ClipBinding,
+                      SieveBinding, SortBinding, CastBinding, RescopingBinding,
                       LiteralBinding, FormulaBinding)
 from .lookup import direct
 from .flow import (RootFlow, ScalarFlow, DirectTableFlow, FiberTableFlow,
                    QuotientFlow, ComplementFlow, MonikerFlow, ForkedFlow,
-                   LinkedFlow, FilteredFlow, OrderedFlow,
+                   LinkedFlow, ClippedFlow, FilteredFlow, OrderedFlow,
                    QueryExpr, SegmentCode, LiteralCode, FormulaCode,
                    CastCode, RecordCode, AnnihilatorCode,
                    ColumnUnit, ScalarUnit, KernelUnit)
         return LinkedFlow(base, seed, images, self.binding)
 
 
+class RelateClip(Relate):
+
+    adapt(ClipBinding)
+
+    def __call__(self):
+        base = self.state.relate(self.binding.base)
+        seed = self.state.relate(self.binding.seed)
+        return ClippedFlow(base, seed, self.binding.limit,
+                           self.binding.offset, self.binding)
+
+
 class EncodeColumn(Encode):
 
     adapt(ColumnBinding)

src/htsql/core/tr/flow.py

                    self.seed, ", ".join(str(rop) for lop, rop in self.images))
 
 
+class ClippedFlow(Flow):
+
+    is_axis = True
+
+    def __init__(self, base, seed, limit, offset, binding, companions=[]):
+        assert isinstance(base, Flow)
+        assert isinstance(seed, Flow)
+        assert seed.spans(base)
+        assert not base.spans(seed)
+        assert isinstance(limit, maybe(int))
+        assert isinstance(offset, maybe(int))
+        assert isinstance(companions, listof(Code))
+        # Determine an axial ancestor of `seed` spanned by `base`.
+        ground = seed
+        while not ground.is_axis:
+            ground = ground.base
+        assert not base.spans(ground)
+        while not base.spans(ground.base):
+            ground = ground.base
+        is_contracting = (limit is None)
+        is_expanding = (seed.dominates(base) and offset is None
+                        and (limit is None or limit > 0))
+        super(ClippedFlow, self).__init__(
+                    base=base,
+                    family=seed.family,
+                    is_contracting=is_contracting,
+                    is_expanding=is_expanding,
+                    binding=binding)
+        self.seed = seed
+        self.ground = ground
+        self.limit = limit
+        self.offset = offset
+        self.companions = companions
+
+    def __basis__(self):
+        return (self.base, self.seed, self.limit, self.offset)
+
+    def __str__(self):
+        # Display:
+        #   (<base> . (<seed>) [<offset>:<offset>+<limit>])
+        return "(%s . (%s) [%s:%s+%s])" \
+                % (self.base, self.seed,
+                   self.offset if self.offset is not None else 0,
+                   self.offset if self.offset is not None else 0,
+                   self.limit if self.limit is not None else 1)
+
+
 class FilteredFlow(Flow):
     """
     Represents a filtering operation.

src/htsql/core/tr/fn/bind.py

                        WrappingBinding, TitleBinding, DirectionBinding,
                        QuotientBinding, AssignmentBinding, DefinitionBinding,
                        SelectionBinding, HomeBinding, RescopingBinding,
-                       CoverBinding, ForkBinding, CommandBinding,
+                       CoverBinding, ForkBinding, ClipBinding, CommandBinding,
                        SegmentBinding, QueryBinding, Binding,
                        BindingRecipe, ComplementRecipe, KernelRecipe,
                        SubstitutionRecipe, ClosedRecipe)
                         RoundSig, RoundToSig, TruncSig, TruncToSig, LengthSig,
                         ContainsSig, ExistsSig, CountSig, MinMaxSig,
                         SumSig, AvgSig, AggregateSig, QuantifySig,
-                        DefineSig, WhereSig, SelectSig, LinkSig)
+                        DefineSig, WhereSig, SelectSig, LinkSig, TopSig)
 from ...cmd.command import RendererCmd, DefaultCmd, RetrieveCmd, SQLCmd
 from ...fmt.format import (RawFormat, JSONFormat, CSVFormat, TSVFormat,
                            HTMLFormat, TextFormat, XMLFormat)
         return ForkBinding(self.state.scope, elements, self.syntax)
 
 
+class BindTop(BindMacro):
+
+    call('top')
+    signature = TopSig
+
+    def parse(self, argument):
+        try:
+            if not isinstance(argument, NumberSyntax):
+                raise ValueError
+            value = int(argument.value)
+            if not (value >= 0):
+                raise ValueError
+            if not isinstance(value, int):
+                raise ValueError
+        except ValueError:
+            raise BindError("function '%s' expects a non-negative integer"
+                            % self.name.encode('utf-8'), argument.mark)
+        return value
+
+    def expand(self, seed, limit=None, offset=None):
+        seed = self.state.bind(seed)
+        if limit is not None:
+            limit = self.parse(limit)
+        if offset is not None:
+            offset = self.parse(offset)
+        return ClipBinding(self.state.scope, seed, limit, offset, self.syntax)
+
+
 class BindDirectionBase(BindMacro):
 
     signature = SortDirectionSig
             value = int(argument.value)
             if not (value >= 0):
                 raise ValueError
+            if not isinstance(value, int):
+                raise ValueError
         except ValueError:
             raise BindError("function '%s' expects a non-negative integer"
                             % self.name.encode('utf-8'), argument.mark)

src/htsql/core/tr/fn/signature.py

     ]
 
 
+class TopSig(Signature):
+
+    slots = [
+            Slot('seed'),
+            Slot('limit', is_mandatory=False),
+            Slot('offset', is_mandatory=False),
+    ]
+
+
 class CastSig(Signature):
 
     slots = [

src/htsql/core/tr/lookup.py

                       SegmentBinding, HomeBinding, RootBinding, TableBinding,
                       FreeTableBinding, AttachedTableBinding,
                       ColumnBinding, QuotientBinding, ComplementBinding,
-                      CoverBinding, ForkBinding, LinkBinding, RescopingBinding,
-                      DefinitionBinding, SelectionBinding, WildSelectionBinding,
-                      DirectionBinding, RerouteBinding, ReferenceRerouteBinding,
-                      TitleBinding, AliasBinding, CommandBinding,
-                      ImplicitCastBinding,
+                      CoverBinding, ForkBinding, LinkBinding, ClipBinding,
+                      RescopingBinding, DefinitionBinding, SelectionBinding,
+                      WildSelectionBinding, DirectionBinding, RerouteBinding,
+                      ReferenceRerouteBinding, TitleBinding, AliasBinding,
+                      CommandBinding, ImplicitCastBinding,
                       FreeTableRecipe, AttachedTableRecipe, ColumnRecipe,
                       ComplementRecipe, KernelRecipe, BindingRecipe,
                       SubstitutionRecipe, ClosedRecipe, InvalidRecipe,
     # Find an attribute in a cover scope.
 
     adapt_many((CoverBinding, AttributeProbe),
-               (CoverBinding, AttributeSetProbe))
+               (CoverBinding, AttributeSetProbe),
+               (CoverBinding, ComplementProbe),
+               (ClipBinding, AttributeProbe),
+               (ClipBinding, AttributeSetProbe),
+               (ClipBinding, ComplementProbe))
 
     def __call__(self):
         # Delegate all lookup requests to the seed flow.
 class ExpandCover(Lookup):
     # Expand public columns from a cover scope.
 
-    adapt(CoverBinding, ExpansionProbe)
+    adapt_many((CoverBinding, ExpansionProbe),
+               (ClipBinding, ExpansionProbe))
 
     def __call__(self):
         # Ignore pure selector expansion probes.

src/htsql/core/tr/rewrite.py

 """
 
 
-from ..adapter import Utility, Adapter, adapt
+from ..adapter import Utility, Adapter, adapt, adapt_many
 from ..domain import BooleanDomain
 from .error import EncodeError
 from .coerce import coerce
 from .flow import (Expression, QueryExpr, SegmentCode, Flow, RootFlow,
                    QuotientFlow, ComplementFlow, MonikerFlow, ForkedFlow,
-                   LinkedFlow, FilteredFlow, OrderedFlow,
+                   LinkedFlow, ClippedFlow, FilteredFlow, OrderedFlow,
                    Code, LiteralCode, CastCode, RecordCode,
                    AnnihilatorCode, FormulaCode, Unit,
                    CompoundUnit, ScalarUnit, AggregateUnitBase, AggregateUnit,
 
 class RewriteMoniker(RewriteFlow):
 
-    adapt(MonikerFlow)
+    adapt_many(MonikerFlow,
+               ClippedFlow)
 
     def __call__(self):
         # Apply the adapter to all child nodes.
 
 class UnmaskMoniker(UnmaskFlow):
 
-    adapt(MonikerFlow)
+    adapt_many(MonikerFlow,
+               ClippedFlow)
 
     def __call__(self):
         # Unmask the seed flow against the parent flow.
 
 class ReplaceMoniker(Replace):
 
-    adapt(MonikerFlow)
+    adapt_many(MonikerFlow,
+               ClippedFlow)
 
     def __call__(self):
         # Replace the parent flow.