Commits

Kirill Simonov committed 4fba7e8

Refactored serialization of the sort order indicator.

Comments (0)

Files changed (13)

src/htsql/core/tr/assemble.py

                     FormulaPhrase, Anchor, LeadingAnchor)
 from .signature import (Signature, IsEqualSig, IsTotallyEqualSig, IsInSig,
                         IsNullSig, NullIfSig, IfNullSig, CompareSig,
-                        AndSig, OrSig, NotSig, ToPredicateSig,
+                        AndSig, OrSig, NotSig, SortDirectionSig, ToPredicateSig,
                         FromPredicateSig)
 
 
             if not code.units:
                 continue
             phrase = self.state.evaluate(code, router=self.term.kid)
-            order.append((phrase, direction))
+            phrase = FormulaPhrase(SortDirectionSig(direction),
+                                   phrase.domain, phrase.is_nullable,
+                                   phrase.expression, base=phrase)
+            order.append(phrase)
         return order
 
     def assemble_limit(self):

src/htsql/core/tr/dump.py

                     FormulaPhrase, Anchor, LeadingAnchor)
 from .signature import (Signature, isformula, IsEqualSig, IsTotallyEqualSig,
                         IsInSig, IsNullSig, IfNullSig, NullIfSig, CompareSig,
-                        AndSig, OrSig, NotSig, ToPredicateSig,
+                        AndSig, OrSig, NotSig, SortDirectionSig, ToPredicateSig,
                         FromPredicateSig)
 from .plan import Plan, Statement
 import StringIO
         self.write(u"GROUP BY ")
         # Write the `GROUP BY` items.
         for index, phrase in enumerate(self.frame.group):
-            # SQL syntax allows us to refer to a `SELECT` column in
-            # a `GROUP BY` clause by position.  Thus, when a `GROUP BY`
-            # element coincides with some `SELECT` phrase, we could avoid
-            # serializing the same phrase twice.
-            if phrase in self.frame.select:
-                position = self.frame.select.index(phrase)+1
-                self.write(unicode(position))
-            # Otherwise, just serialize the phrase.
-            else:
-                self.format("{kernel}", kernel=phrase)
+            self.format(u"{kernel}", kernel=phrase)
             # Dump the trailing comma.
             if index < len(self.frame.group)-1:
                 self.write(u", ")
     def dump_order(self):
         # Serialize an `ORDER BY` clause.  Dump:
         #   ORDER BY <phrase> (ASC|DESC), ...
-        # Note: the default serializer assumes that the `ASC` modifier
-        # lists `NULL` values first, and the `DESC` modifier lists `NULL`
-        # values last.  Backends for which it is not so must override
-        # this method.
 
         # Nothing to write if there is no `ORDER BY` items.
         if not self.frame.order:
         self.newline()
         self.write(u"ORDER BY ")
         # Write the `GROUP BY` items.
-        for index, (phrase, direction) in enumerate(self.frame.order):
-            # Just as with `GROUP BY`, an `ORDER BY` item could refer
-            # to a `SELECT` column by position.  We do it when possible
-            # to avoid serializing the same phrase node twice.
-            if phrase in self.frame.select:
-                position = self.frame.select.index(phrase)+1
-                self.write(unicode(position))
-            # The regular case: serialize the node.
-            else:
-                self.format("{kernel}", kernel=phrase)
-            # Write the direction modifier.
-            self.format(" {direction:switch{ASC|DESC}}", direction=direction)
+        for index, phrase in enumerate(self.frame.order):
+            self.format("{kernel}", kernel=phrase)
             # Write the trailing comma.
             if index < len(self.frame.order)-1:
                 self.write(u", ")
                     self.arguments, relation=unicode(self.signature.relation))
 
 
+class DumpSortDirection(DumpBySignature):
+
+    adapt(SortDirectionSig)
+
+    def __call__(self):
+        # Note: the default serializer assumes that the `ASC` modifier
+        # lists `NULL` values first, and the `DESC` modifier lists `NULL`
+        # values last.  Backends for which it is not so must override
+        # this adapter.
+        self.format("{base} {direction:switch{ASC|DESC}}", self.arguments,
+                    self.signature)
+
+
 class DumpToPredicate(DumpBySignature):
 
     adapt(ToPredicateSig)

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

 from ..signature import (Signature, NullarySig, UnarySig, BinarySig,
                          CompareSig, IsEqualSig, IsTotallyEqualSig, IsInSig,
                          IsNullSig, IfNullSig, NullIfSig, AndSig, OrSig,
-                         NotSig)
-from .signature import (AsSig, SortDirectionSig, LimitSig, SortSig, CastSig,
+                         NotSig, SortDirectionSig)
+from .signature import (AsSig, LimitSig, SortSig, CastSig,
                         MakeDateSig, MakeDateTimeSig, CombineDateTimeSig,
                         ExtractYearSig, ExtractMonthSig, ExtractDaySig,
                         ExtractHourSig, ExtractMinuteSig, ExtractSecondSig,

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

     ]
 
 
-class SortDirectionSig(Signature):
-
-    slots = [
-            Slot('base'),
-    ]
-
-    def __init__(self, direction):
-        assert direction in [+1, -1]
-        self.direction = direction
-
-    def __basis__(self):
-        return (self.direction,)
-
-
 class LimitSig(Signature):
 
     slots = [

src/htsql/core/tr/frame.py

     `having` (:class:`Phrase` or ``None``)
         Represents the ``HAVING`` clause.
 
-    `order` (a list of pairs `(phrase, direction)`)
+    `order` (a list of :class:`Phrase`)
         Represents the ``ORDER BY`` clause.
 
-        Here `phrase` is a :class:`Phrase` instance, `direction`
-        is either ``+1`` (indicates ascending order) or ``-1``
-        (indicates descending order).
-
     `limit` (a non-negative integer or ``None``)
         Represents the ``LIMIT`` clause.
 
         assert isinstance(where, maybe(Phrase))
         assert isinstance(group, listof(Phrase))
         assert isinstance(having, maybe(Phrase))
-        assert isinstance(order, listof(tupleof(Phrase, int)))
+        assert isinstance(order, listof(Phrase))
         assert isinstance(limit, maybe(int))
         assert isinstance(offset, maybe(int))
         assert limit is None or limit >= 0

src/htsql/core/tr/reduce.py

 
 
 from ..adapter import Adapter, adapt
-from ..domain import BooleanDomain, StringDomain
+from ..domain import BooleanDomain, StringDomain, IntegerDomain
 from .coerce import coerce
 from .stitch import arrange
 from .term import PermanentTerm
                     ExportPhrase, ReferencePhrase, Anchor, LeadingAnchor)
 from .signature import (Signature, isformula, IsEqualSig, IsTotallyEqualSig,
                         IsInSig, IsNullSig, IfNullSig, NullIfSig,
-                        AndSig, OrSig, NotSig, FromPredicateSig,
-                        ToPredicateSig)
+                        AndSig, OrSig, NotSig, SortDirectionSig,
+                        FromPredicateSig, ToPredicateSig)
 
 
 class ReducingState(object):
         # Realize and apply the `Collapse` adapter.
         return Collapse.__invoke__(frame, self)
 
+    def interlink(self, frame):
+        return Interlink.__invoke__(frame, self)
+
     def to_predicate(self, phrase):
         phrase = FormulaPhrase(ToPredicateSig(), phrase.domain,
                                phrase.is_nullable, phrase.expression,
 
     def reduce_embed(self):
         # Collapse and reduce the embedded subframes.
-        return [self.state.reduce(self.state.collapse(frame))
+        return [self.state.interlink(
+                self.state.reduce(
+                self.state.collapse(frame)))
                 for frame in self.frame.embed]
 
     def reduce_select(self):
         # also eliminate duplicates and literals.
         order = []
         duplicates = set()
-        for phrase, direction in self.frame.order:
+        for phrase in self.frame.order:
             phrase = self.state.reduce(phrase)
-            if isinstance(phrase, LiteralPhrase):
-                continue
-            if phrase in duplicates:
-                continue
-            order.append((phrase, direction))
-            duplicates.add(phrase)
+            if isformula(phrase, SortDirectionSig):
+                if isinstance(phrase.base, LiteralPhrase):
+                    continue
+                if phrase.base in duplicates:
+                    continue
+            order.append(phrase)
+            duplicates.add(phrase.base)
         return order
 
     def __call__(self):
         for subframe in frame.subtrees:
             subframe = self.state.collapse(subframe)
             subframe = self.state.reduce(subframe)
+            subframe = self.state.interlink(subframe)
             subtrees.append(subframe)
         return frame.clone(subtrees=subtrees)
 
         return self.state.collapse(frame)
 
 
+class Interlink(Adapter):
+
+    adapt(Frame)
+
+    def __init__(self, frame, state):
+        assert isinstance(frame, Frame)
+        assert isinstance(state, ReducingState)
+        self.frame = frame
+        self.state = state
+
+    def __call__(self):
+        return self.frame
+
+
+class InterlinkBranch(Interlink):
+
+    adapt(BranchFrame)
+
+    def interlink_group(self):
+        group = []
+        for index, phrase in enumerate(self.frame.group):
+            if phrase in self.frame.select:
+                position = self.frame.select.index(phrase)+1
+                phrase = LiteralPhrase(position, coerce(IntegerDomain()),
+                                       phrase.expression)
+            group.append(phrase)
+        return group
+
+    def interlink_order(self):
+        order = []
+        for index, phrase in enumerate(self.frame.order):
+            if isformula(phrase, SortDirectionSig):
+                if phrase.base in self.frame.select:
+                    position = self.frame.select.index(phrase.base)+1
+                    base = LiteralPhrase(position, coerce(IntegerDomain()),
+                                         phrase.base.expression)
+                    phrase = phrase.clone(base=base)
+            order.append(phrase)
+        return order
+
+    def __call__(self):
+        group = self.interlink_group()
+        order = self.interlink_order()
+        return self.frame.clone(group=group, order=order)
+
+
 class ReduceAnchor(Reduce):
     """
     Reduces a ``JOIN`` clause.
 
     def __call__(self):
         # Collapse and reduce (in that order!) the subframe.
-        frame = self.state.reduce(self.state.collapse(self.clause.frame))
+        frame = self.state.interlink(
+                self.state.reduce(
+                self.state.collapse(self.clause.frame)))
         # Reduce the join condition.
         condition = (self.state.reduce(self.clause.condition)
                      if self.clause.condition is not None else None)
         segment = self.clause.segment
         segment = self.state.collapse(segment)
         segment = self.state.reduce(segment)
+        segment = self.state.interlink(segment)
         # Clear the state variables.
         self.state.flush()
         # Update the query frame.

src/htsql/core/tr/signature.py

     """
 
 
+class SortDirectionSig(Signature):
+
+    slots = [
+            Slot('base'),
+    ]
+
+    def __init__(self, direction):
+        assert direction in [+1, -1]
+        self.direction = direction
+
+    def __basis__(self):
+        return (self.direction,)
+
+
 class ToPredicateSig(UnarySig):
     pass
 

src/htsql_mssql/core/tr/compile.py

 from htsql.core.tr.term import PermanentTerm, FilterTerm
 from htsql.core.tr.flow import LiteralCode, FormulaCode, ScalarUnit
 from htsql.core.tr.coerce import coerce
-from htsql.core.tr.signature import CompareSig, AndSig
-from htsql.core.tr.fn.signature import SortDirectionSig
+from htsql.core.tr.signature import CompareSig, AndSig, SortDirectionSig
 from .signature import RowNumberSig
 from htsql.core.tr.compile import CompileOrdered
 from htsql.core.tr.stitch import arrange, spread
                           self.flow, kid.baseline, routes)
 
 
-

src/htsql_mssql/core/tr/dump.py

                                    DumpDateIncrement, DumpDateDecrement,
                                    DumpDateTimeIncrement,
                                    DumpDateTimeDecrement, DumpDateDifference)
-from htsql.core.tr.fn.signature import SortDirectionSig
 from .signature import RowNumberSig
 import math
 
                 self.newline()
         self.dedent()
 
-    def dump_group(self):
-        if not self.frame.group:
-            return
-        self.newline()
-        self.write(u"GROUP BY ")
-        for index, phrase in enumerate(self.frame.group):
-            self.format("{kernel}", kernel=phrase)
-            if index < len(self.frame.group)-1:
-                self.write(u", ")
-
     def dump_limit(self):
         assert self.frame.offset is None
 
                     self.arguments)
 
 
-class MSSQLDumpSortDirection(DumpBySignature):
-
-    adapt(SortDirectionSig)
-
-    def __call__(self):
-        self.format("{base} {direction:switch{ASC|DESC}}",
-                    self.arguments, self.signature)
-
-
 class MSSQLDumpBoolean(DumpBoolean):
 
     def __call__(self):

src/htsql_mssql/core/tr/reduce.py

 
 
 from htsql.core.tr.signature import isformula, FromPredicateSig, ToPredicateSig
-from htsql.core.tr.reduce import ReduceFromPredicate, ReduceToPredicate
+from htsql.core.tr.reduce import (ReduceFromPredicate, ReduceToPredicate,
+                                  InterlinkBranch)
+
+
+class MSSQLInterlinkBranch(InterlinkBranch):
+
+    def interlink_group(self):
+        return self.frame.group
 
 
 class MSSQLReduceFromPredicate(ReduceFromPredicate):

src/htsql_oracle/core/tr/dump.py

                                 DumpFloat, DumpTime, DumpDateTime,
                                 DumpToFloat, DumpToDecimal, DumpToString,
                                 DumpToDate, DumpToTime, DumpToDateTime,
-                                DumpIsTotallyEqual, DumpBySignature)
+                                DumpIsTotallyEqual, DumpBySignature,
+                                DumpSortDirection)
 from htsql.core.tr.fn.dump import (DumpLength, DumpSubstring, DumpDateIncrement,
                                    DumpDateDecrement, DumpDateDifference,
                                    DumpMakeDate, DumpCombineDateTime,
 
 class OracleDumpBranch(DumpBranch):
 
-    def dump_group(self):
-        if not self.frame.group:
-            return
-        self.newline()
-        self.write(u"GROUP BY ")
-        for index, phrase in enumerate(self.frame.group):
-            self.format("{kernel}", kernel=phrase)
-            if index < len(self.frame.group)-1:
-                self.write(u", ")
-
-    def dump_order(self):
-        if not self.frame.order:
-            return
-        self.newline()
-        self.write(u"ORDER BY ")
-        for index, (phrase, direction) in enumerate(self.frame.order):
-            if phrase in self.frame.select:
-                position = self.frame.select.index(phrase)+1
-                self.write(unicode(position))
-            else:
-                self.format("{kernel}", kernel=phrase)
-            self.format(" {direction:switch{ASC|DESC}}", direction=direction)
-            if phrase.is_nullable:
-                self.format(" NULLS {direction:switch{FIRST|LAST}}",
-                            direction=direction)
-            if index < len(self.frame.order)-1:
-                self.write(u", ")
-
     def dump_limit(self):
         assert self.frame.limit is None
         assert self.frame.offset is None
 
 
+class OracleDumpSortDirection(DumpSortDirection):
+
+    def __call__(self):
+        self.format("{base} {direction:switch{ASC|DESC}}",
+                    self.arguments, self.signature)
+        if self.phrase.is_nullable:
+            self.format(" NULLS {direction:switch{FIRST|LAST}}",
+                        self.signature)
+
+
 class OracleDumpLeadingAnchor(DumpLeadingAnchor):
 
     def __call__(self):

src/htsql_oracle/core/tr/reduce.py

 from htsql.core.tr.signature import isformula, ToPredicateSig, FromPredicateSig
 from htsql.core.tr.frame import ScalarFrame, NullPhrase, LeadingAnchor
 from htsql.core.tr.reduce import (ReduceScalar, ReduceBranch, ReduceLiteral,
-                                  ReduceFromPredicate, ReduceToPredicate)
+                                  ReduceFromPredicate, ReduceToPredicate,
+                                  InterlinkBranch)
+
+
+class OracleInterlinkBranch(InterlinkBranch):
+
+    def interlink_group(self):
+        return self.frame.group
 
 
 class OracleReduceScalar(ReduceScalar):

src/htsql_pgsql/core/tr/dump.py

 
 
 from htsql.core.domain import IntegerDomain
-from htsql.core.tr.dump import (FormatLiteral, DumpBranch, DumpFloat,
-                                DumpDecimal, DumpDate, DumpTime, DumpDateTime,
-                                DumpToDecimal, DumpToFloat, DumpToString)
+from htsql.core.tr.dump import (FormatLiteral, DumpFloat, DumpDecimal, DumpDate,
+                                DumpTime, DumpDateTime, DumpToDecimal,
+                                DumpToFloat, DumpToString, DumpSortDirection)
 from htsql.core.tr.fn.dump import (DumpLike, DumpDateIncrement,
                                    DumpDateDecrement, DumpDateDifference,
                                    DumpMakeDate, DumpExtractYear,
             self.stream.write(u"'%s'" % value)
 
 
-class PGSQLDumpBranch(DumpBranch):
+class PGSQLDumpSortDirection(DumpSortDirection):
 
-    def dump_order(self):
-        if not self.frame.order:
-            return
-        self.newline()
-        self.write(u"ORDER BY ")
-        for index, (phrase, direction) in enumerate(self.frame.order):
-            if phrase in self.frame.select:
-                position = self.frame.select.index(phrase)+1
-                self.write(unicode(position))
-            else:
-                self.format("{kernel}", kernel=phrase)
-            self.format(" {direction:switch{ASC|DESC}}", direction=direction)
-            if phrase.is_nullable:
-                self.format(" NULLS {direction:switch{FIRST|LAST}}",
-                            direction=direction)
-            if index < len(self.frame.order)-1:
-                self.write(u", ")
+    def __call__(self):
+        self.format("{base} {direction:switch{ASC|DESC}}",
+                    self.arguments, self.signature)
+        if self.phrase.is_nullable:
+            self.format(" NULLS {direction:switch{FIRST|LAST}}",
+                        self.signature)
 
 
 class PGSQLDumpFloat(DumpFloat):