Commits

Kirill Simonov  committed f686166

Added title decorator: `as`

  • Participants
  • Parent commits 207a234

Comments (0)

Files changed (10)

File src/htsql/fmt/entitle.py

+#
+# Copyright (c) 2006-2010, Prometheus Research, LLC
+# Authors: Clark C. Evans <cce@clarkevans.com>,
+#          Kirill Simonov <xi@resolvent.net>
+#
+
+
+"""
+:mod:`htsql.fmt.entitle`
+========================
+
+This module implements the entitle adapter.
+"""
+
+
+from ..util import setof
+from ..adapter import Adapter, Utility, adapts, adapts_many
+from ..tr.binding import (Binding, RootBinding, SieveBinding, SortBinding,
+                          CastBinding, WrapperBinding, TitleBinding)
+
+
+class Entitle(Adapter):
+
+    adapts(Binding)
+
+    def __init__(self, binding, with_strong, with_weak):
+        self.binding = binding
+        self.with_strong = with_strong
+        self.with_weak = with_weak
+
+    def __call__(self):
+        if self.with_weak:
+            return str(self.binding.syntax)
+        return None
+
+
+class EntitleWrapper(Entitle):
+
+    adapts_many(SieveBinding, SortBinding, WrapperBinding)
+
+    def __call__(self):
+        if self.with_strong:
+            title = entitle(self.binding.base, with_weak=False)
+            if title is not None:
+                return title
+        return super(EntitleWrapper, self).__call__()
+
+
+class EntitleCast(Entitle):
+
+    adapts(CastBinding)
+
+    def __call__(self):
+        if self.with_strong:
+            title = entitle(self.binding.op, with_weak=False)
+            if title is not None:
+                return title
+        return super(EntitleCast, self).__call__()
+
+
+class EntitleTitle(Entitle):
+
+    adapts(TitleBinding)
+
+    def __call__(self):
+        if self.with_strong:
+            return self.binding.title
+        return super(EntitleTitle, self).__call__()
+
+
+class EntitleRoot(Entitle):
+
+    adapts(RootBinding)
+
+    def __call__(self):
+        if self.with_weak:
+            return ""
+        return super(EntitleRoot, self).__call__()
+
+
+def entitle(binding, with_strong=True, with_weak=True):
+    entitle = Entitle(binding, with_strong, with_weak)
+    return entitle()
+
+

File src/htsql/fmt/format.py

 
 
 """
-:mod:`htsql.format`
-===================
+:mod:`htsql.fmt.format`
+=======================
 
 This module implements the format adapter.
 """

File src/htsql/fmt/html.py

 
 from ..adapter import adapts
 from .format import Format, Formatter, Renderer
+from .entitle import entitle
 from ..domain import (Domain, BooleanDomain, NumberDomain,
                       StringDomain, EnumDomain, DateDomain)
 import cgi
 
     def calculate_layout(self, product, formats):
         segment = product.profile.binding.segment
-        caption = str(segment.base.syntax).decode('utf-8')
-        headers = [str(element.syntax).decode('utf-8')
+        caption = entitle(segment.base).decode('utf-8')
+        headers = [entitle(element).decode('utf-8')
                    for element in segment.elements]
         column_widths = [len(header) for header in headers]
         total = 0
         yield "</table>\n"
 
     def serialize_content(self, product):
-        caption = str(product.profile.segment.binding.base.syntax)
-        headers = [str(element.syntax)
+        caption = entitle(product.profile.segment.binding.base)
+        headers = [entitle(element)
                    for element in product.profile.segment.elements]
         width = len(product.profile.segment.elements)
         domains = [element.domain

File src/htsql/fmt/text.py

 from ..adapter import adapts
 from ..util import maybe, oneof
 from .format import Format, Formatter, Renderer
+from .entitle import entitle
 from ..domain import (Domain, BooleanDomain, NumberDomain, IntegerDomain,
                       DecimalDomain, FloatDomain, StringDomain, EnumDomain,
                       DateDomain)
 
     def calculate_layout(self, product, formats):
         segment = product.profile.binding.segment
-        caption = str(segment.base.syntax).decode('utf-8')
-        headers = [str(element.syntax).decode('utf-8')
+        caption = entitle(segment.base).decode('utf-8')
+        headers = [entitle(element).decode('utf-8')
                    for element in segment.elements]
         column_widths = [len(header) for header in headers]
         total = 0

File src/htsql/tr/binding.py

         self.ops = ops
 
 
-
 class ConjunctionBinding(ConnectiveBindingBase):
     """
     Represents the logical "AND" (``&``) operator.

File src/htsql/tr/fn/function.py

 from ...domain import (Domain, UntypedDomain, BooleanDomain, StringDomain,
                        NumberDomain, IntegerDomain, DecimalDomain, FloatDomain,
                        DateDomain)
-from ..syntax import NumberSyntax
+from ..syntax import NumberSyntax, StringSyntax, IdentifierSyntax
 from ..binding import (LiteralBinding, SortBinding, FunctionBinding,
                        EqualityBinding, TotalEqualityBinding,
                        ConjunctionBinding, DisjunctionBinding, NegationBinding,
-                       CastBinding)
+                       CastBinding, TitleBinding)
 from ..encoder import Encoder, Encode
 from ..code import (FunctionCode, NegationCode, AggregateUnit,
                     CorrelatedUnit, LiteralCode, FilteredSpace)
         return self.check_arguments(arguments)
 
 
+class AsFunction(ProperFunction):
+
+    named('as')
+
+    parameters = [
+            Parameter('base'),
+            Parameter('title', StringDomain),
+    ]
+
+    def bind_arguments(self):
+        if len(self.syntax.arguments) != 2:
+            raise InvalidArgumentError("expected two arguments",
+                                       self.syntax.mark)
+        base = self.state.bind(self.syntax.arguments[0])
+        title_syntax = self.syntax.arguments[1]
+        if not isinstance(title_syntax, (StringSyntax, IdentifierSyntax)):
+            raise InvalidArgumentError("expected a string literal"
+                                       " or an identifier",
+                                       title_syntax.mark)
+        return {'base': base, 'title': title_syntax.value}
+
+    def correlate(self, base, title):
+        yield TitleBinding(base, title, self.syntax)
+
+
 class LimitMethod(ProperMethod):
 
     named('limit')

File src/htsql/tr/parse.py

         #   call            ::= '(' elements? ')'
         #   elements        ::= element ( ',' element )* ','?
         expression = AtomParser << tokens
-        while tokens.peek(SymbolToken, ['.']):
-            head_token = tokens.pop(SymbolToken, ['.'])
+        while tokens.peek(SymbolToken, ['.'], do_pop=True):
             if tokens.peek(SymbolToken, ['*']):
                 symbol_token = tokens.pop(SymbolToken, ['*'])
                 wildcard = WildcardSyntax(symbol_token.mark)
-                mark = Mark.union(head_token, wildcard)
+                mark = Mark.union(expression, wildcard)
                 expression = SpecifierSyntax(expression, wildcard, mark)
                 break
             else:
                         if not tokens.peek(SymbolToken, [')']):
                             tokens.pop(SymbolToken, [',', ')'])
                     tail_token = tokens.pop(SymbolToken, [')'])
-                    mark = Mark.union(head_token, tail_token)
+                    mark = Mark.union(expression, tail_token)
                     expression = FunctionCallSyntax(expression, identifier,
                                                     arguments, mark)
                 else:
-                    mark = Mark.union(head_token, identifier)
+                    mark = Mark.union(expression, identifier)
                     expression = SpecifierSyntax(expression, identifier, mark)
         return expression
 

File test/input/pgsql.yaml

                  date('2010-07-28')-date('2009-07-28')}
 
   # Order and Limit functions.
-  - title: Sorting and paging.
+  - title: Sorting and paging
     tests:
     - uri: /school
     - uri: /school.limit(1)
     - uri: /course.order(credits)?department='acc'
     - uri: /course.order(credits).limit(1,1)?department='acc'
 
+  # The `as` operator.
+  - title: Title decorator
+    tests:
+    # Identifiers and string literals are accepted.
+    - uri: /{null() as Title, null() as 'Title with whitespaces'}
+    # The outer `as` overrides any others.
+    - uri: /{null() as 'Hidden title' as 'Visible title'}
+    # `as` in expressions is no-op.
+    - uri: /{('HT' as HT)+('SQL' as SQL)}
+    # Using `as` to decorate the segment base.
+    - uri: /(school as Schools)
+    # Also works over selectors and filters.
+    - uri: /(school as Schools){name as Title}?code='art'
+    # Expects a non-wildcard argument.
+    - uri: /school{* as Columns}
+      expect: 400
+    # Expects a string literal or an identifier.
+    - uri: /school{code as school.code}
+      expect: 400
 
   # Simple (non-aggregate) filters.
   - title: Simple filters

File test/output/pgsql.yaml

       headers:
       - [Content-Type, text/plain; charset=UTF-8]
       body: |2
-         | /{'HT'+'SQL'} |
-        -+---------------+-
-         | 'HT'+'SQL'    |
-        -+---------------+-
-         | HTSQL         |
-                   (1 row)
+         |            |
+        -+------------+-
+         | 'HT'+'SQL' |
+        -+------------+-
+         | HTSQL      |
+                (1 row)
 
          ----
          /{'HT'+'SQL'}
         status: 200 OK
         headers:
         - [Content-Type, text/plain; charset=UTF-8]
-        body: " | /{'','HTSQL','O''Reilly','\u03BB\u03CC\u03B3\u03BF\u03C2','%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F','%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F'}
-          \                                                                                |\n-+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-\n
+        body: " |                                                                                                                                                                                                                      |\n-+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-\n
           | '' | 'HTSQL' | 'O''Reilly' | '\u03BB\u03CC\u03B3\u03BF\u03C2' | '%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F'
           \                         | '%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F'
           \                                                |\n-+----+---------+-------------+---------+--------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------+-\n
         headers:
         - [Content-Type, text/plain; charset=UTF-8]
         body: |2
-           | /{0,1,100,65536}    |
+           |                     |
           -+---------------------+-
            | 0 | 1 | 100 | 65536 |
           -+---+---+-----+-------+-
         headers:
         - [Content-Type, text/plain; charset=UTF-8]
         body: |2
-           | /{0.0,1.0,3.14,0.00000000001,1234567890.0987654321}      |
+           |                                                          |
           -+----------------------------------------------------------+-
            | 0.0 | 1.0 | 3.14 | 0.00000000001 | 1234567890.0987654321 |
           -+-----+-----+------+---------------+-----------------------+-
         headers:
         - [Content-Type, text/plain; charset=UTF-8]
         body: |2
-           | /{10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.1} |
-          -+------------------------------------------------------------------------------------------------------------+-
-           | 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.1    |
-          -+------------------------------------------------------------------------------------------------------------+-
-           |    10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.1 |
-                                                                                                                  (1 row)
+           |                                                                                                         |
+          -+---------------------------------------------------------------------------------------------------------+-
+           | 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.1 |
+          -+---------------------------------------------------------------------------------------------------------+-
+           | 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.1 |
+                                                                                                               (1 row)
 
            ----
            /{10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.1}
         headers:
         - [Content-Type, text/plain; charset=UTF-8]
         body: |2
-           | /{0e0,1e1,0.31415926535897931e1,2718281828459045e-16}     |
+           |                                                           |
           -+-----------------------------------------------------------+-
            | 0e0 | 1e1  | 0.31415926535897931e1 | 2718281828459045e-16 |
           -+-----+------+-----------------------+----------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{true(),false()} |
-            -+-------------------+-
-             | true()  | false() |
-            -+---------+---------+-
-             | true    | false   |
-                           (1 row)
+             |                  |
+            -+------------------+-
+             | true() | false() |
+            -+--------+---------+-
+             | true   | false   |
+                          (1 row)
 
              ----
              /{true(),false()}
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{boolean(null()),boolean(true()),boolean(false())}  |
+             |                                                      |
             -+------------------------------------------------------+-
              | boolean(null()) | boolean(true()) | boolean(false()) |
             -+-----------------+-----------------+------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{boolean('true'),boolean('false')} |
-            -+-------------------------------------+-
-             | boolean('true')  | boolean('false') |
-            -+------------------+------------------+-
-             | true             | false            |
-                                             (1 row)
+             |                                    |
+            -+------------------------------------+-
+             | boolean('true') | boolean('false') |
+            -+-----------------+------------------+-
+             | true            | false            |
+                                            (1 row)
 
              ----
              /{boolean('true'),boolean('false')}
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{boolean(integer(null())),boolean(0),boolean(1),boolean(0.0),boolean(1.0),boolean(0e0),boolean(1e0)}          |
+             |                                                                                                                |
             -+----------------------------------------------------------------------------------------------------------------+-
              | boolean(integer(null())) | boolean(0) | boolean(1) | boolean(0.0) | boolean(1.0) | boolean(0e0) | boolean(1e0) |
             -+--------------------------+------------+------------+--------------+--------------+--------------+--------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{boolean(string(null())),boolean(string('')),boolean(string('X')),boolean(string('true')),boolean(string('false'))}      |
+             |                                                                                                                           |
             -+---------------------------------------------------------------------------------------------------------------------------+-
              | boolean(string(null())) | boolean(string('')) | boolean(string('X')) | boolean(string('true')) | boolean(string('false')) |
             -+-------------------------+---------------------+----------------------+-------------------------+--------------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{false()&false(),false()&true(),true()&false(),true()&true(),null()&false(),null()&true(),null()&null()}          |
+             |                                                                                                                    |
             -+--------------------------------------------------------------------------------------------------------------------+-
              | false()&false() | false()&true() | true()&false() | true()&true() | null()&false() | null()&true() | null()&null() |
             -+-----------------+----------------+----------------+---------------+----------------+---------------+---------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{string('')&string('X'),0&1} |
+             |                               |
             -+-------------------------------+-
              | string('')&string('X') | 0&1  |
             -+------------------------+------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{null()&null(),integer(null())&null(),integer(null())&integer(null())}  |
+             |                                                                          |
             -+--------------------------------------------------------------------------+-
              | null()&null() | integer(null())&null() | integer(null())&integer(null()) |
             -+---------------+------------------------+---------------------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{false()|false(),false()|true(),true()|false(),true()|true(),null()|false(),null()|true(),null()|null()}          |
+             |                                                                                                                    |
             -+--------------------------------------------------------------------------------------------------------------------+-
              | false()|false() | false()|true() | true()|false() | true()|true() | null()|false() | null()|true() | null()|null() |
             -+-----------------+----------------+----------------+---------------+----------------+---------------+---------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{string('')|string('X'),0|1,integer(null())|string(null())}   |
+             |                                                                |
             -+----------------------------------------------------------------+-
              | string('')|string('X') | 0|1  | integer(null())|string(null()) |
             -+------------------------+------+--------------------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{!true(),!false(),!null()}  |
+             |                              |
             -+------------------------------+-
              | !true() | !false() | !null() |
             -+---------+----------+---------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{!string(''),!string('X'),!1,!integer(null())}       |
+             |                                                       |
             -+-------------------------------------------------------+-
              | !string('') | !string('X') | !1    | !integer(null()) |
             -+-------------+--------------+-------+------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{is_null(null()),is_null(true()),is_null(''),is_null(0)}    |
+             |                                                              |
             -+--------------------------------------------------------------+-
              | is_null(null()) | is_null(true()) | is_null('') | is_null(0) |
             -+-----------------+-----------------+-------------+------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{'Y'.null_if('X'),'Y'.null_if('Y'),(5).null_if(1,2,3),(5).null_if(1,2,3,4,5,6,7,8,9,10),null().null_if(null()),null().null_if('X')}        |
+             |                                                                                                                                             |
             -+---------------------------------------------------------------------------------------------------------------------------------------------+-
              | 'Y'.null_if('X') | 'Y'.null_if('Y') | (5).null_if(1,2,3) | (5).null_if(1,2,3,4,5,6,7,8,9,10) | null().null_if(null()) | null().null_if('X') |
             -+------------------+------------------+--------------------+-----------------------------------+------------------------+---------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{'X'.if_null('Y'),null().if_null('X'),null().if_null(null()),null().if_null(null(),null(),null()),null().if_null(null(),null(),0),null().if_null(0,1,2,3,null())}        |
+             |                                                                                                                                                                           |
             -+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-
              | 'X'.if_null('Y') | null().if_null('X') | null().if_null(null()) | null().if_null(null(),null(),null()) | null().if_null(null(),null(),0) | null().if_null(0,1,2,3,null()) |
             -+------------------+---------------------+------------------------+--------------------------------------+---------------------------------+--------------------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{if(true(),1),if(false(),1),if(null(),1),if(true(),1,0),if(false(),1,0),if(null(),1,0),if(true(),1,true(),2),if(true(),1,false(),2),if(false(),1,true(),2),if(false(),1,false(),2),if(false(),1,false(),2,0)}                  |
+             |                                                                                                                                                                                                                                 |
             -+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-
              | if(true(),1) | if(false(),1) | if(null(),1) | if(true(),1,0) | if(false(),1,0) | if(null(),1,0) | if(true(),1,true(),2) | if(true(),1,false(),2) | if(false(),1,true(),2) | if(false(),1,false(),2) | if(false(),1,false(),2,0) |
             -+--------------+---------------+--------------+----------------+-----------------+----------------+-----------------------+------------------------+------------------------+-------------------------+---------------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{switch('Y','X',1),switch('Y','Y',1),switch('Y','X',1,0),switch('Y','Y',1,0),switch(null(),null(),1,0),switch('Y','X',1,'Y',2,'Z',3),switch('Y','A',1,'B',2,'C',3,0)}          |
+             |                                                                                                                                                                                 |
             -+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-
              | switch('Y','X',1) | switch('Y','Y',1) | switch('Y','X',1,0) | switch('Y','Y',1,0) | switch(null(),null(),1,0) | switch('Y','X',1,'Y',2,'Z',3) | switch('Y','A',1,'B',2,'C',3,0) |
             -+-------------------+-------------------+---------------------+---------------------+---------------------------+-------------------------------+---------------------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{1=1,1=0,1=null(),null()=null(),1!=1,1!=0,1!=null(),null()!=null(),1==1,1==0,1==null(),null()==null(),1!==1,1!==0,1!==null(),null()!==null()}                                 |
+             |                                                                                                                                                                                |
             -+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-
              | 1=1  | 1=0   | 1=null() | null()=null() | 1!=1  | 1!=0 | 1!=null() | null()!=null() | 1==1 | 1==0  | 1==null() | null()==null() | 1!==1 | 1!==0 | 1!==null() | null()!==null() |
             -+------+-------+----------+---------------+-------+------+-----------+----------------+------+-------+-----------+----------------+-------+-------+------------+-----------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{'X'='X',1=1.0,1=1e0,1.0=1e0,1='1'}      |
+             |                                           |
             -+-------------------------------------------+-
              | 'X'='X' | 1=1.0 | 1=1e0 | 1.0=1e0 | 1='1' |
             -+---------+-------+-------+---------+-------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{'X'<'Y','X'<='Y','X'>'Y','X'>='Y','X'<null(),'X'<=null(),'X'>null(),'X'>=null()}            |
+             |                                                                                               |
             -+-----------------------------------------------------------------------------------------------+-
              | 'X'<'Y' | 'X'<='Y' | 'X'>'Y' | 'X'>='Y' | 'X'<null() | 'X'<=null() | 'X'>null() | 'X'>=null() |
             -+---------+----------+---------+----------+------------+-------------+------------+-------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{1<2,1<=2,1>2,1>=2,1<2.0,1<=2.0,1>2.0,1>=2.0,1e0<2.0,1e0<=2.0,1e0>2.0,1e0>2.0}                        |
+             |                                                                                                        |
             -+--------------------------------------------------------------------------------------------------------+-
              | 1<2  | 1<=2 | 1>2   | 1>=2  | 1<2.0 | 1<=2.0 | 1>2.0 | 1>=2.0 | 1e0<2.0 | 1e0<=2.0 | 1e0>2.0 | 1e0>2.0 |
             -+------+------+-------+-------+-------+--------+-------+--------+---------+----------+---------+---------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{integer('1'),integer(string('1')),integer(1),integer(1.0),integer(1e0)}      |
+             |                                                                                |
             -+--------------------------------------------------------------------------------+-
              | integer('1') | integer(string('1')) | integer(1) | integer(1.0) | integer(1e0) |
             -+--------------+----------------------+------------+--------------+--------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{integer(65536.0),integer(65.536),integer(655.36),integer(65536e0),integer(65536e-3),integer(65535e-2)}        |
+             |                                                                                                                 |
             -+-----------------------------------------------------------------------------------------------------------------+-
              | integer(65536.0) | integer(65.536) | integer(655.36) | integer(65536e0) | integer(65536e-3) | integer(65535e-2) |
             -+------------------+-----------------+-----------------+------------------+-------------------+-------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{decimal('1.5'),decimal(string('1.5')),decimal(15),decimal(1.5),decimal(15e-1)}      |
+             |                                                                                       |
             -+---------------------------------------------------------------------------------------+-
              | decimal('1.5') | decimal(string('1.5')) | decimal(15) | decimal(1.5) | decimal(15e-1) |
             -+----------------+------------------------+-------------+--------------+----------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{decimal(65535e0),decimal(65535e10),decimal(65535e-10)}  |
+             |                                                           |
             -+-----------------------------------------------------------+-
              | decimal(65535e0) | decimal(65535e10) | decimal(65535e-10) |
             -+------------------+-------------------+--------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{decimal(1e300),decimal(1e-300)}                                                                                                                                                                                                                                                                                               |
+             |                                                                                                                                                                                                                                                                                                                                 |
             -+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-
              | decimal(1e300)                                                                                                                                                                                                                                                                                                | decimal(1e-300) |
             -+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{float('1.5'),float(string('1.5')),float(15),float(1.5),float(15e-1)}      |
+             |                                                                             |
             -+-----------------------------------------------------------------------------+-
              | float('1.5') | float(string('1.5')) | float(15) | float(1.5) | float(15e-1) |
             -+--------------+----------------------+-----------+------------+--------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{float(123456789.123456789)} |
-            -+-------------------------------+-
-             | float(123456789.123456789)    |
-            -+-------------------------------+-
-             |                 123456789.123 |
-                                       (1 row)
+             |                            |
+            -+----------------------------+-
+             | float(123456789.123456789) |
+            -+----------------------------+-
+             |              123456789.123 |
+                                    (1 row)
 
              ----
              /{float(123456789.123456789)}
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{+2,+2.0,+2e0,-2,-2.0,-2e0,++1,+-1,-+1,--1}                |
+             |                                                             |
             -+-------------------------------------------------------------+-
              | +2 | +2.0 | +2e0 | -2 | -2.0 | -2e0 | ++1 | +-1 | -+1 | --1 |
             -+----+------+------+----+------+------+-----+-----+-----+-----+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{2+2,2+2.0,2+2e0,2.0+2.0,2.0+2e0,2e0+2e0}        |
+             |                                                   |
             -+---------------------------------------------------+-
              | 2+2 | 2+2.0 | 2+2e0 | 2.0+2.0 | 2.0+2e0 | 2e0+2e0 |
             -+-----+-------+-------+---------+---------+---------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{2-1,2-1.0,2-1e0,2.0-1.0,2.0-1e0,2e0-1e0}        |
+             |                                                   |
             -+---------------------------------------------------+-
              | 2-1 | 2-1.0 | 2-1e0 | 2.0-1.0 | 2.0-1e0 | 2e0-1e0 |
             -+-----+-------+-------+---------+---------+---------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{5*5,5*5.0,5*5e0,5.0*5.0,5.0*5e0,5e0*5e0}        |
+             |                                                   |
             -+---------------------------------------------------+-
              | 5*5 | 5*5.0 | 5*5e0 | 5.0*5.0 | 5.0*5e0 | 5e0*5e0 |
             -+-----+-------+-------+---------+---------+---------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{1/2,1/2.0,1/2e0,1.0/2.0,1.0/2e0,1e0/2e0}                                                           |
+             |                                                                                                      |
             -+------------------------------------------------------------------------------------------------------+-
              | 1/2                    | 1/2.0                  | 1/2e0 | 1.0/2.0                | 1.0/2e0 | 1e0/2e0 |
             -+------------------------+------------------------+-------+------------------------+---------+---------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{round(65.536),round(65.536,0),round(65.536,1),round(65.536,-1)}    |
+             |                                                                      |
             -+----------------------------------------------------------------------+-
              | round(65.536) | round(65.536,0) | round(65.536,1) | round(65.536,-1) |
             -+---------------+-----------------+-----------------+------------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{round(65535),round(65536,-3)} |
-            -+---------------------------------+-
-             | round(65535)  | round(65536,-3) |
-            -+---------------+-----------------+-
-             |         65535 |           66000 |
-                                         (1 row)
+             |                                |
+            -+--------------------------------+-
+             | round(65535) | round(65536,-3) |
+            -+--------------+-----------------+-
+             |        65535 |           66000 |
+                                        (1 row)
 
              ----
              /{round(65535),round(65536,-3)}
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{round(35536e-3)} |
-            -+--------------------+-
-             | round(35536e-3)    |
-            -+--------------------+-
-             |               36.0 |
-                            (1 row)
+             |                 |
+            -+-----------------+-
+             | round(35536e-3) |
+            -+-----------------+-
+             |            36.0 |
+                         (1 row)
 
              ----
              /{round(35536e-3)}
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{string('X'),string(string('X')),string(1),string(1.0),string(1e0)}      |
+             |                                                                           |
             -+---------------------------------------------------------------------------+-
              | string('X') | string(string('X')) | string(1) | string(1.0) | string(1e0) |
             -+-------------+---------------------+-----------+-------------+-------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{'HTSQL'.length(),''.length(),null().length()}  |
+             |                                                  |
             -+--------------------------------------------------+-
              | 'HTSQL'.length() | ''.length() | null().length() |
             -+------------------+-------------+-----------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{'HTSQL'~'sql','HTSQL'~'HTTP','HTSQL'~'','HTSQL'~null(),null()~'HTSQL',null()~null()}        |
+             |                                                                                               |
             -+-----------------------------------------------------------------------------------------------+-
              | 'HTSQL'~'sql' | 'HTSQL'~'HTTP' | 'HTSQL'~'' | 'HTSQL'~null() | null()~'HTSQL' | null()~null() |
             -+---------------+----------------+------------+----------------+----------------+---------------+-
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{date('2010-07-28'),date(string('2010-07-28'))} |
-            -+--------------------------------------------------+-
-             | date('2010-07-28')  | date(string('2010-07-28')) |
-            -+---------------------+----------------------------+-
-             | 2010-07-28          | 2010-07-28                 |
-                                                          (1 row)
+             |                                                 |
+            -+-------------------------------------------------+-
+             | date('2010-07-28') | date(string('2010-07-28')) |
+            -+--------------------+----------------------------+-
+             | 2010-07-28         | 2010-07-28                 |
+                                                         (1 row)
 
              ----
              /{date('2010-07-28'),date(string('2010-07-28'))}
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{date(2010,07,28)} |
-            -+---------------------+-
-             | date(2010,07,28)    |
-            -+---------------------+-
-             | 2010-07-28          |
-                             (1 row)
+             |                  |
+            -+------------------+-
+             | date(2010,07,28) |
+            -+------------------+-
+             | 2010-07-28       |
+                          (1 row)
 
              ----
              /{date(2010,07,28)}
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | /{date('2010-07-28')+1,date('2010-07-28')-1,date('2010-07-28')-date('2009-07-28')}  |
+             |                                                                                     |
             -+-------------------------------------------------------------------------------------+-
              | date('2010-07-28')+1 | date('2010-07-28')-1 | date('2010-07-28')-date('2009-07-28') |
             -+----------------------+----------------------+---------------------------------------+-
              ----
              /{date('2010-07-28')+1,date('2010-07-28')-1,date('2010-07-28')-date('2009-07-28')}
              SELECT (CAST('2010-07-28' AS DATE) + 1), (CAST('2010-07-28' AS DATE) - 1), (CAST('2010-07-28' AS DATE) - CAST('2009-07-28' AS DATE))
-  - id: sorting-and-paging.
+  - id: sorting-and-paging
     tests:
     - uri: /school
       status: 200 OK
          ----
          /course.order(credits).limit(1,1)?department='acc'
          SELECT "course"."department", "course"."number", "course"."title", "course"."credits", "course"."description" FROM (SELECT "course"."department", "course"."number", "course"."title", "course"."credits", "course"."description" FROM (SELECT "course"."department", "course"."number", "course"."title", "course"."credits", "course"."description" FROM "ad"."course" AS "course" ORDER BY 4 ASC, 1 ASC, 2 ASC) AS "course" WHERE ("course"."department" = 'acc') ORDER BY 4 ASC, 1 ASC, 2 ASC LIMIT 1 OFFSET 1) AS "course" ORDER BY 4 ASC, 1 ASC, 2 ASC
+  - id: title-decorator
+    tests:
+    - uri: /{null() as Title, null() as 'Title with whitespaces'}
+      status: 200 OK
+      headers:
+      - [Content-Type, text/plain; charset=UTF-8]
+      body: |2
+         |                                |
+        -+--------------------------------+-
+         | Title | Title with whitespaces |
+        -+-------+------------------------+-
+         |       |                        |
+                                    (1 row)
+
+         ----
+         /{null() as Title,null() as 'Title with whitespaces'}
+         SELECT NULL, NULL
+    - uri: /{null() as 'Hidden title' as 'Visible title'}
+      status: 200 OK
+      headers:
+      - [Content-Type, text/plain; charset=UTF-8]
+      body: |2
+         |               |
+        -+---------------+-
+         | Visible title |
+        -+---------------+-
+         |               |
+                   (1 row)
+
+         ----
+         /{null() as 'Hidden title' as 'Visible title'}
+         SELECT NULL
+    - uri: /{('HT' as HT)+('SQL' as SQL)}
+      status: 200 OK
+      headers:
+      - [Content-Type, text/plain; charset=UTF-8]
+      body: |2
+         |                             |
+        -+-----------------------------+-
+         | ('HT' as HT)+('SQL' as SQL) |
+        -+-----------------------------+-
+         | HTSQL                       |
+                                 (1 row)
+
+         ----
+         /{('HT' as HT)+('SQL' as SQL)}
+         SELECT ('HT' || 'SQL')
+    - uri: /(school as Schools)
+      status: 200 OK
+      headers:
+      - [Content-Type, text/plain; charset=UTF-8]
+      body: |2
+         | Schools                                 |
+        -+-----------------------------------------+-
+         | code | name                             |
+        -+------+----------------------------------+-
+         | art  | School of Art and Design         |
+         | bus  | School of Business               |
+         | edu  | College of Education             |
+         | egn  | School of Engineering            |
+         | la   | School of Arts, Letters, and the |
+         :      : Humanities                       :
+         | mart | School of Modern Art             |
+         | mus  | Musical School                   |
+         | ns   | School of Natural Sciences       |
+         | sc   | School of Continuing Studies     |
+                                            (9 rows)
+
+         ----
+         /(school as Schools)
+         SELECT "school"."code", "school"."name" FROM "ad"."school" AS "school" ORDER BY 1 ASC
+    - uri: /(school as Schools){name as Title}?code='art'
+      status: 200 OK
+      headers:
+      - [Content-Type, text/plain; charset=UTF-8]
+      body: |2
+         | Schools                  |
+        -+--------------------------+-
+         | Title                    |
+        -+--------------------------+-
+         | School of Art and Design |
+                              (1 row)
+
+         ----
+         /(school as Schools){name as Title}?code='art'
+         SELECT "school"."name" FROM "ad"."school" AS "school" WHERE ("school"."code" = 'art') ORDER BY "school"."code" ASC
+    - uri: /school{* as Columns}
+      status: 400 Bad Request
+      headers:
+      - [Content-Type, text/plain; charset=UTF-8]
+      body: |
+        bind error: unexpected selector or wildcard expression:
+            /school{* as Columns}
+                    ^
+    - uri: /school{code as school.code}
+      status: 400 Bad Request
+      headers:
+      - [Content-Type, text/plain; charset=UTF-8]
+      body: |
+        invalid argument: expected a string literal or an identifier:
+            /school{code as school.code}
+                            ^^^^^^^^^^^
   - id: simple-filters
     tests:
     - uri: /school?code='ns'
       headers:
       - [Content-Type, text/plain; charset=UTF-8]
       body: |2
-         | /{count(school),count(department),count(course)}  |
+         |                                                   |
         -+---------------------------------------------------+-
          | count(school) | count(department) | count(course) |
         -+---------------+-------------------+---------------+-
       headers:
       - [Content-Type, text/plain; charset=UTF-8]
       body: |2
-         | /{count(department),count(department?exists(course))} |
-        -+-------------------------------------------------------+-
-         | count(department)  | count(department?exists(course)) |
-        -+--------------------+----------------------------------+-
-         |                 24 |                               20 |
-                                                           (1 row)
+         |                                                      |
+        -+------------------------------------------------------+-
+         | count(department) | count(department?exists(course)) |
+        -+-------------------+----------------------------------+-
+         |                24 |                               20 |
+                                                          (1 row)
 
          ----
          /{count(department),count(department?exists(course))}

File test/output/sqlite.yaml

       headers:
       - [Content-Type, text/plain; charset=UTF-8]
       body: |2
-         | /{2+2} |
-        -+--------+-
-         | 2+2    |
-        -+--------+-
-         |      4 |
-            (1 row)
+         |       |
+        -+-------+-
+         | 2+2   |
+        -+-------+-
+         |     4 |
+           (1 row)
 
          ----
          /{2+2}
       headers:
       - [Content-Type, text/plain; charset=UTF-8]
       body: |2
-         | /{'HT'+'SQL'} |
-        -+---------------+-
-         | 'HT'+'SQL'    |
-        -+---------------+-
-         | HTSQL         |
-                   (1 row)
+         |            |
+        -+------------+-
+         | 'HT'+'SQL' |
+        -+------------+-
+         | HTSQL      |
+                (1 row)
 
          ----
          /{'HT'+'SQL'}