Commits

Kirill Simonov committed b54c823

Added basic support for references in locators.

Comments (0)

Files changed (14)

src/htsql/core/fmt/json.py

 from ..adapter import Adapter, Protocol, adapt, adapt_many, call
 from ..domain import (Domain, BooleanDomain, NumberDomain, FloatDomain,
         StringDomain, EnumDomain, DateDomain, TimeDomain, DateTimeDomain,
-        ListDomain, RecordDomain, UntypedDomain, VoidDomain, OpaqueDomain,
-        Profile)
+        ListDomain, RecordDomain, IdentityDomain, UntypedDomain, VoidDomain,
+        OpaqueDomain, Profile)
 from .format import RawFormat, JSONFormat, EmitHeaders, Emit
 import re
 import math
         yield JS_END
 
 
+class IdentityDomainToRaw(DomainToRaw):
+
+    adapt(IdentityDomain)
+
+    def __call__(self):
+        yield JS_MAP
+        yield u"type"
+        yield unicode(self.domain.family)
+        #yield u"fields"
+        #yield JS_SEQ
+        #for field in self.domain.fields:
+        #    yield JS_MAP
+        #    yield u"domain"
+        #    for token in domain_to_raw(field):
+        #        yield token
+        #    yield JS_END
+        #yield JS_END
+        yield JS_END
+
+
 class ToJSON(Adapter):
 
     adapt(Domain)

src/htsql/core/tr/bind.py

         AmbiguousRecipe)
 from .lookup import (lookup_attribute, lookup_reference, lookup_complement,
         lookup_attribute_set, lookup_reference_set, expand, direct, guess_tag,
-        lookup_command, identify)
+        lookup_command, identify, unwrap)
 from .coerce import coerce
 from .decorate import decorate
 
         if recipe is None:
             raise BindError("cannot determine identity", seed.mark)
         identity = self.state.use(recipe, self.syntax.rbranch, scope=seed)
-        if identity.domain.arity != self.syntax.rbranch.arity:
+        location = self.state.bind(self.syntax.rbranch, scope=seed)
+        if identity.domain.arity != location.arity:
             raise BindError("ill-formed locator", self.syntax.rbranch.mark)
-        def convert(identity, branches):
+        def convert(identity, elements):
             value = []
             for field in identity.fields:
                 if isinstance(field, IdentityDomain):
                     total_arity = 0
                     items = []
                     while total_arity < field.arity:
-                        assert branches
-                        branch = branches.pop(0)
+                        assert elements
+                        element = elements.pop(0)
                         if (total_arity == 0 and
-                                isinstance(branch, LocationSyntax) and
-                                branch.arity == field.arity):
-                            items = branch.branches[:]
-                            total_arity = branch.arity
-                        elif isinstance(branch, LocationSyntax):
-                            items.append(branch)
-                            total_arity += branch.arity
+                                isinstance(element, IdentityBinding) and
+                                element.arity == field.arity):
+                            items = element.elements[:]
+                            total_arity = element.arity
+                        elif isinstance(element, IdentityBinding):
+                            items.append(element)
+                            total_arity += element.arity
                         else:
-                            items.append(branch)
+                            items.append(element)
                             total_arity += 1
                     if total_arity > field.arity:
                         raise BindError("ill-formed locator",
                     item = convert(field, items)
                     value.append(item)
                 else:
-                    assert branches
-                    branch = branches.pop(0)
-                    if not isinstance(branch, StringSyntax):
+                    assert elements
+                    element = elements.pop(0)
+                    if isinstance(element, IdentityBinding):
                         raise BindError("ill-formed locator",
                                         self.syntax.lbranch.mark)
-                    try:
-                        item = field.parse(branch.value)
-                    except ValueError, exc:
-                        raise BindError(str(exc), branch.mark)
-                    item = LiteralBinding(seed, item, field, branch)
+                    item = ImplicitCastBinding(element, field, element.syntax)
                     value.append(item)
             return tuple(value)
-        value = convert(identity.domain, self.syntax.rbranch.branches[:])
+        elements = location.elements[:]
+        while len(elements) == 1 and isinstance(elements[0], IdentityBinding):
+            elements = elements[0].elements[:]
+        value = convert(identity.domain, elements)
         return LocatorBinding(self.state.scope, seed, identity, value,
                               self.syntax)
 
     adapt(LocationSyntax)
 
     def __call__(self):
-        elements = [self.state.bind(branch)
-                    for branch in self.syntax.branches]
+        elements = []
+        for branch in self.syntax.branches:
+            element = self.state.bind(branch)
+            identity = unwrap(element, IdentityBinding, is_deep=False)
+            if identity is not None:
+                element = identity
+            elements.append(element)
         return IdentityBinding(self.state.scope, elements, self.syntax)
 
 

src/htsql/core/tr/binding.py

         domain = IdentityDomain([element.domain for element in elements])
         super(IdentityBinding, self).__init__(base, domain, syntax)
         self.elements = elements
+        self.arity = domain.arity
 
 
 class AssignmentBinding(Binding):

src/htsql/core/tr/parse.py

         specifier       ::= locator ( '.' locator )*
         locator         ::= atom ( '[' location ']' )?
         location        ::= label ( '.' label )*
-        label           ::= STRING | '(' location ')' | '[' location ']'
+        label           ::= STRING | '(' location ')' | '[' location ']' |
+                            reference
 
         atom            ::= '@' atom | '*' index? | '^' | selector | group |
                             '[' location ']' | identifier call? | reference |
     @classmethod
     def process(cls, tokens):
         # Expect:
-        #   label           ::= STRING | '(' location ')' | '[' location ']'
+        #   label           ::= STRING | '(' location ')' | '[' location ']' |
+        #                       reference
         if tokens.peek(UnquotedStringToken):
             token = tokens.pop(UnquotedStringToken)
             return UnquotedStringSyntax(token.value, token.mark)
         elif tokens.peek(SymbolToken, [u'[', u'(']):
             label = LocationParser << tokens
             return label
+        elif tokens.peek(SymbolToken, [u'$']):
+            head_token = tokens.pop(SymbolToken, [u'$'])
+            if not tokens.peek(NameToken):
+                raise ParseError("symbol '$' must be followed by an identifier",
+                                 head_token.mark)
+            identifier = IdentifierParser << tokens
+            mark = Mark.union(head_token, identifier)
+            label = ReferenceSyntax(identifier, mark)
+            return label
         if tokens.peek(EndToken):
             token = tokens.pop(EndToken)
             raise ParseError("unexpected end of query", token.mark)

src/htsql/core/tr/scan.py

                          jump='locator'),
                 ScanRule(r""" \] | \) """, SymbolToken,
                          is_end=True),
+                ScanRule(r""" \$ """, SymbolToken,
+                         jump='identifier'),
                 ScanRule(r""" \. """, SymbolToken),
                 ScanRule(r""" [\w-]+ """, UnquotedStringToken),
                 ScanRule(r""" ' (?: [^'\0] | '')* ' """, StringToken,
                          error="""cannot find a matching quote mark"""),
                 ScanRule(r""" $ """, None,
                          error="""cannot find a matching ']'""")),
+            ScanGroup('identifier',
+                ScanRule(r""" (?! \d) \w+ """, NameToken,
+                         is_end=True)),
     ]
 
     # The regular expression to match %-escape sequences.

src/htsql/core/tr/syntax.py

 
     def __init__(self, branches, mark):
         assert isinstance(branches, listof(oneof(LocationSyntax,
+                                                 ReferenceSyntax,
                                                  StringSyntax)))
         super(LocationSyntax, self).__init__(mark)
         self.branches = branches
-        self.arity = 0
-        for branch in self.branches:
-            if isinstance(branch, LocationSyntax):
-                self.arity += branch.arity
-            else:
-                self.arity += 1
 
     def __basis__(self):
         return (tuple(self.branches),)

src/htsql/tweak/etl/cmd/do.py

 
 from ....core.adapter import Adapter, adapt
 from ....core.connect import transaction
+from ....core.domain import IdentityDomain
 from ....core.cmd.command import DefaultCmd
 from ....core.cmd.act import Act, ProduceAction, produce
 from ....core.cmd.fetch import Product
         ReferenceSyntax)
 from ....core.tr.bind import BindingState
 from ....core.tr.binding import (VoidBinding, QueryBinding, SegmentBinding,
-        LiteralBinding, DefinitionBinding, BindingRecipe, ClosedRecipe)
+        LiteralBinding, IdentityBinding, DefinitionBinding, BindingRecipe,
+        ClosedRecipe)
 from ....core.tr.decorate import decorate
 from ....core.tr.lookup import lookup_command
 from ....core.tr.error import BindError
                     command = DefaultCmd(binding)
                 product = produce(command)
                 if reference is not None:
-                    literal = LiteralBinding(state.scope, product.data,
-                                             product.meta.domain, reference)
+                    if (isinstance(product.meta.domain, IdentityDomain) and
+                            product.data is not None):
+                        def convert(domain, data):
+                            items = []
+                            for element, item in zip(domain.fields, data):
+                                if isinstance(element, IdentityDomain):
+                                    item = convert(element, item)
+                                else:
+                                    item = LiteralBinding(state.scope, item,
+                                                          element, reference)
+                                items.append(item)
+                            return IdentityBinding(state.scope, items,
+                                                   reference)
+                        literal = convert(product.meta.domain, product.data)
+                    else:
+                        literal = LiteralBinding(state.scope, product.data,
+                                                 product.meta.domain, reference)
                     recipe = ClosedRecipe(BindingRecipe(literal))
         return product
 

src/htsql/tweak/etl/cmd/insert.py

                     arc = label.arc
                     idx = index_by_arc[arc]
                     field = fields[idx]
-                    if label.arc in arcs:
+                    if (isinstance(arc, ColumnArc) and arc.link is not None
+                            and isinstance(field.domain, IdentityDomain)):
+                        arc = arc.link
+                    if arc in arcs:
                         raise BadRequestError("duplicate field %s"
                                               % field.tag.encode('utf-8'))
                     arcs.append(arc)

test/input/translation.yaml

     - uri: /department{id(), school{id()}}
     - uri: /course[astro.105].class.id()
     - uri: /program^degree{*, /^.id()}
+    - uri: /school[$id] :where $id:='ns'
+    - uri: /course[$id]{id(), title} :where $id:=[astro.105]
+    - uri: /class[$course.$season.001]
+            :where ($course:=[astro.105], $season:=[2009.spring])
     - uri: /program[ns]
       expect: 400
     - uri: /school[ns.uchem]
       expect: 400
     - uri: /program^degree{id()}
       expect: 400
+    - uri: /course[$id]{id(), title} :where $id:='astro.105'
+      expect: 400
 
   - title: Table Expressions
     tests:

test/output/mssql.yaml

                  FROM [ad].[program]
                  WHERE ([program].[degree] IS NOT NULL)
                  ORDER BY 3 ASC, 1 ASC, 2 ASC
+          - uri: /school[$id] :where $id:='ns'
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | school                                     |
+               +------+----------------------------+--------+
+               | code | name                       | campus |
+              -+------+----------------------------+--------+-
+               | ns   | School of Natural Sciences | old    |
+
+               ----
+               /school[$id]:where($id:='ns')
+               SELECT [school].[code],
+                      [school].[name],
+                      [school].[campus]
+               FROM [ad].[school]
+               WHERE ([school].[code] = 'ns')
+               ORDER BY 1 ASC
+          - uri: /course[$id]{id(), title} :where $id:=[astro.105]
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | course                          |
+               +-----------+---------------------+
+               | id()      | title               |
+              -+-----------+---------------------+-
+               | astro.105 | General Astronomy I |
+
+               ----
+               /course[$id]{id(),title}:where($id:=[astro.105])
+               SELECT [course].[department_code],
+                      [course].[no],
+                      [course].[title]
+               FROM [ad].[course]
+               WHERE ([course].[department_code] = 'astro')
+                     AND ([course].[no] = 105)
+               ORDER BY 1 ASC, 2 ASC
+          - uri: /class[$course.$season.001] :where ($course:=[astro.105], $season:=[2009.spring])
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | class                                                                               |
+               +-----------------+-----------+------+--------+---------+-----------------+-----------+
+               | department_code | course_no | year | season | section | instructor_code | class_seq |
+              -+-----------------+-----------+------+--------+---------+-----------------+-----------+-
+               | astro           |       105 | 2009 | spring | 001     | mtriplett       |      1167 |
+
+               ----
+               /class[$course.$season.001]:where($course:=[astro.105],$season:=[2009.spring])
+               SELECT [class].[department_code],
+                      [class].[course_no],
+                      [class].[year],
+                      [class].[season],
+                      [class].[section],
+                      [class].[instructor_code],
+                      [class].[class_seq]
+               FROM [cd].[class]
+               WHERE ([class].[department_code] = 'astro')
+                     AND ([class].[course_no] = 105)
+                     AND ([class].[year] = 2009.)
+                     AND ([class].[season] = 'spring')
+                     AND ([class].[section] = '001')
+               ORDER BY 1 ASC, 2 ASC, 3 ASC, 4 ASC, 5 ASC
           - uri: /program[ns]
             status: 400 Bad Request
             headers:
             headers:
             - [Content-Type, text/plain; charset=UTF-8]
             body: |
-              bind error: invalid integer literal: expected an integer in a decimal format; got 'uchem':
+              encode error: invalid integer literal: expected an integer in a decimal format; got 'uchem':
                   /course[ns.uchem]
                              ^^^^^
           - uri: /program^degree{id()}
               bind error: cannot determine identity:
                   /program^degree{id()}
                                   ^^^^
+          - uri: /course[$id]{id(), title} :where $id:='astro.105'
+            status: 400 Bad Request
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            body: |
+              bind error: ill-formed locator:
+                  /course[$id]{id(), title} :where $id:='astro.105'
+                         ^^^^^
         - id: table-expressions
           tests:
           - uri: /(school?code='art').department

test/output/mysql.yaml

                  FROM `program`
                  WHERE (`program`.`degree` IS NOT NULL)
                  ORDER BY 3 ASC, 1 ASC, 2 ASC
+          - uri: /school[$id] :where $id:='ns'
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | school                                     |
+               +------+----------------------------+--------+
+               | code | name                       | campus |
+              -+------+----------------------------+--------+-
+               | ns   | School of Natural Sciences | old    |
+
+               ----
+               /school[$id]:where($id:='ns')
+               SELECT `school`.`code`,
+                      `school`.`name`,
+                      `school`.`campus`
+               FROM `school`
+               WHERE (`school`.`code` = 'ns')
+               ORDER BY 1 ASC
+          - uri: /course[$id]{id(), title} :where $id:=[astro.105]
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | course                          |
+               +-----------+---------------------+
+               | id()      | title               |
+              -+-----------+---------------------+-
+               | astro.105 | General Astronomy I |
+
+               ----
+               /course[$id]{id(),title}:where($id:=[astro.105])
+               SELECT `course`.`department_code`,
+                      `course`.`no`,
+                      `course`.`title`
+               FROM `course`
+               WHERE (`course`.`department_code` = 'astro')
+                     AND (`course`.`no` = 105)
+               ORDER BY 1 ASC, 2 ASC
+          - uri: /class[$course.$season.001] :where ($course:=[astro.105], $season:=[2009.spring])
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | class                                                                               |
+               +-----------------+-----------+------+--------+---------+-----------------+-----------+
+               | department_code | course_no | year | season | section | instructor_code | class_seq |
+              -+-----------------+-----------+------+--------+---------+-----------------+-----------+-
+               | astro           |       105 | 2009 | spring | 001     | mtriplett       |      1167 |
+
+               ----
+               /class[$course.$season.001]:where($course:=[astro.105],$season:=[2009.spring])
+               SELECT `class`.`department_code`,
+                      `class`.`course_no`,
+                      `class`.`year`,
+                      `class`.`season`,
+                      `class`.`section`,
+                      `class`.`instructor_code`,
+                      `class`.`class_seq`
+               FROM `class`
+               WHERE (`class`.`department_code` = 'astro')
+                     AND (`class`.`course_no` = 105)
+                     AND (`class`.`year` = 2009.)
+                     AND (`class`.`season` = 'spring')
+                     AND (`class`.`section` = '001')
+               ORDER BY 1 ASC, 2 ASC, 3 ASC, 4 ASC, 5 ASC
           - uri: /program[ns]
             status: 400 Bad Request
             headers:
             headers:
             - [Content-Type, text/plain; charset=UTF-8]
             body: |
-              bind error: invalid integer literal: expected an integer in a decimal format; got 'uchem':
+              encode error: invalid integer literal: expected an integer in a decimal format; got 'uchem':
                   /course[ns.uchem]
                              ^^^^^
           - uri: /program^degree{id()}
               bind error: cannot determine identity:
                   /program^degree{id()}
                                   ^^^^
+          - uri: /course[$id]{id(), title} :where $id:='astro.105'
+            status: 400 Bad Request
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            body: |
+              bind error: ill-formed locator:
+                  /course[$id]{id(), title} :where $id:='astro.105'
+                         ^^^^^
         - id: table-expressions
           tests:
           - uri: /(school?code='art').department

test/output/oracle.yaml

                  FROM "PROGRAM"
                  WHERE ("PROGRAM"."DEGREE" IS NOT NULL)
                  ORDER BY 3 ASC NULLS FIRST, 1 ASC, 2 ASC
+          - uri: /school[$id] :where $id:='ns'
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | school                                     |
+               +------+----------------------------+--------+
+               | code | name                       | campus |
+              -+------+----------------------------+--------+-
+               | ns   | School of Natural Sciences | old    |
+
+               ----
+               /school[$id]:where($id:='ns')
+               SELECT "SCHOOL"."CODE",
+                      "SCHOOL"."NAME",
+                      "SCHOOL"."CAMPUS"
+               FROM "SCHOOL"
+               WHERE ("SCHOOL"."CODE" = 'ns')
+               ORDER BY 1 ASC
+          - uri: /course[$id]{id(), title} :where $id:=[astro.105]
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | course                          |
+               +-----------+---------------------+
+               | id()      | title               |
+              -+-----------+---------------------+-
+               | astro.105 | General Astronomy I |
+
+               ----
+               /course[$id]{id(),title}:where($id:=[astro.105])
+               SELECT "COURSE"."DEPARTMENT_CODE",
+                      "COURSE"."NO",
+                      "COURSE"."TITLE"
+               FROM "COURSE"
+               WHERE ("COURSE"."DEPARTMENT_CODE" = 'astro')
+                     AND ("COURSE"."NO" = 105)
+               ORDER BY 1 ASC, 2 ASC
+          - uri: /class[$course.$season.001] :where ($course:=[astro.105], $season:=[2009.spring])
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | class                                                                               |
+               +-----------------+-----------+------+--------+---------+-----------------+-----------+
+               | department_code | course_no | year | season | section | instructor_code | class_seq |
+              -+-----------------+-----------+------+--------+---------+-----------------+-----------+-
+               | astro           |       105 | 2009 | spring | 001     | mtriplett       |      1167 |
+
+               ----
+               /class[$course.$season.001]:where($course:=[astro.105],$season:=[2009.spring])
+               SELECT "CLASS"."DEPARTMENT_CODE",
+                      "CLASS"."COURSE_NO",
+                      "CLASS"."YEAR",
+                      "CLASS"."SEASON",
+                      "CLASS"."SECTION",
+                      "CLASS"."INSTRUCTOR_CODE",
+                      "CLASS"."CLASS_SEQ"
+               FROM "CLASS"
+               WHERE ("CLASS"."DEPARTMENT_CODE" = 'astro')
+                     AND ("CLASS"."COURSE_NO" = 105)
+                     AND ("CLASS"."YEAR" = 2009)
+                     AND ("CLASS"."SEASON" = 'spring')
+                     AND ("CLASS"."SECTION" = '001')
+               ORDER BY 1 ASC, 2 ASC, 3 ASC, 4 ASC, 5 ASC
           - uri: /program[ns]
             status: 400 Bad Request
             headers:
             headers:
             - [Content-Type, text/plain; charset=UTF-8]
             body: |
-              bind error: invalid decimal literal: uchem:
+              encode error: invalid decimal literal: uchem:
                   /course[ns.uchem]
                              ^^^^^
           - uri: /program^degree{id()}
               bind error: cannot determine identity:
                   /program^degree{id()}
                                   ^^^^
+          - uri: /course[$id]{id(), title} :where $id:='astro.105'
+            status: 400 Bad Request
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            body: |
+              bind error: ill-formed locator:
+                  /course[$id]{id(), title} :where $id:='astro.105'
+                         ^^^^^
         - id: table-expressions
           tests:
           - uri: /(school?code='art').department

test/output/pgsql.yaml

                  FROM "ad"."program"
                  WHERE ("program"."degree" IS NOT NULL)
                  ORDER BY 3 ASC NULLS FIRST, 1 ASC, 2 ASC
+          - uri: /school[$id] :where $id:='ns'
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | school                                     |
+               +------+----------------------------+--------+
+               | code | name                       | campus |
+              -+------+----------------------------+--------+-
+               | ns   | School of Natural Sciences | old    |
+
+               ----
+               /school[$id]:where($id:='ns')
+               SELECT "school"."code",
+                      "school"."name",
+                      "school"."campus"
+               FROM "ad"."school"
+               WHERE ("school"."code" = 'ns')
+               ORDER BY 1 ASC
+          - uri: /course[$id]{id(), title} :where $id:=[astro.105]
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | course                          |
+               +-----------+---------------------+
+               | id()      | title               |
+              -+-----------+---------------------+-
+               | astro.105 | General Astronomy I |
+
+               ----
+               /course[$id]{id(),title}:where($id:=[astro.105])
+               SELECT "course"."department_code",
+                      "course"."no",
+                      "course"."title"
+               FROM "ad"."course"
+               WHERE ("course"."department_code" = 'astro')
+                     AND ("course"."no" = 105)
+               ORDER BY 1 ASC, 2 ASC
+          - uri: /class[$course.$season.001] :where ($course:=[astro.105], $season:=[2009.spring])
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | class                                                                               |
+               +-----------------+-----------+------+--------+---------+-----------------+-----------+
+               | department_code | course_no | year | season | section | instructor_code | class_seq |
+              -+-----------------+-----------+------+--------+---------+-----------------+-----------+-
+               | astro           |       105 | 2009 | spring | 001     | mtriplett       |      1167 |
+
+               ----
+               /class[$course.$season.001]:where($course:=[astro.105],$season:=[2009.spring])
+               SELECT "class"."department_code",
+                      "class"."course_no",
+                      "class"."year",
+                      "class"."season",
+                      "class"."section",
+                      "class"."instructor_code",
+                      "class"."class_seq"
+               FROM "cd"."class"
+               WHERE ("class"."department_code" = 'astro')
+                     AND ("class"."course_no" = 105)
+                     AND ("class"."year" = 2009::NUMERIC)
+                     AND ("class"."season" = 'spring')
+                     AND ("class"."section" = '001')
+               ORDER BY 1 ASC, 2 ASC, 3 ASC, 4 ASC, 5 ASC
           - uri: /program[ns]
             status: 400 Bad Request
             headers:
             headers:
             - [Content-Type, text/plain; charset=UTF-8]
             body: |
-              bind error: invalid integer literal: expected an integer in a decimal format; got 'uchem':
+              encode error: invalid integer literal: expected an integer in a decimal format; got 'uchem':
                   /course[ns.uchem]
                              ^^^^^
           - uri: /program^degree{id()}
               bind error: cannot determine identity:
                   /program^degree{id()}
                                   ^^^^
+          - uri: /course[$id]{id(), title} :where $id:='astro.105'
+            status: 400 Bad Request
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            body: |
+              bind error: ill-formed locator:
+                  /course[$id]{id(), title} :where $id:='astro.105'
+                         ^^^^^
         - id: table-expressions
           tests:
           - uri: /(school?code='art').department

test/output/sqlite.yaml

                  FROM "program"
                  WHERE ("program"."degree" IS NOT NULL)
                  ORDER BY 3 ASC, 1 ASC, 2 ASC
+          - uri: /school[$id] :where $id:='ns'
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | school                                     |
+               +------+----------------------------+--------+
+               | code | name                       | campus |
+              -+------+----------------------------+--------+-
+               | ns   | School of Natural Sciences | old    |
+
+               ----
+               /school[$id]:where($id:='ns')
+               SELECT "school"."code",
+                      "school"."name",
+                      "school"."campus"
+               FROM "school"
+               WHERE ("school"."code" = 'ns')
+               ORDER BY 1 ASC
+          - uri: /course[$id]{id(), title} :where $id:=[astro.105]
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | course                          |
+               +-----------+---------------------+
+               | id()      | title               |
+              -+-----------+---------------------+-
+               | astro.105 | General Astronomy I |
+
+               ----
+               /course[$id]{id(),title}:where($id:=[astro.105])
+               SELECT "course"."department_code",
+                      "course"."no",
+                      "course"."title"
+               FROM "course"
+               WHERE ("course"."department_code" = 'astro')
+                     AND ("course"."no" = 105)
+               ORDER BY 1 ASC, 2 ASC
+          - uri: /class[$course.$season.001] :where ($course:=[astro.105], $season:=[2009.spring])
+            status: 200 OK
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            - [Vary, Accept]
+            body: |2
+               | class                                                                               |
+               +-----------------+-----------+------+--------+---------+-----------------+-----------+
+               | department_code | course_no | year | season | section | instructor_code | class_seq |
+              -+-----------------+-----------+------+--------+---------+-----------------+-----------+-
+               | astro           |       105 | 2009 | spring | 001     | mtriplett       |      1167 |
+
+               ----
+               /class[$course.$season.001]:where($course:=[astro.105],$season:=[2009.spring])
+               SELECT "class"."department_code",
+                      "class"."course_no",
+                      "class"."year",
+                      "class"."season",
+                      "class"."section",
+                      "class"."instructor_code",
+                      "class"."class_seq"
+               FROM "class"
+               WHERE ("class"."department_code" = 'astro')
+                     AND ("class"."course_no" = 105)
+                     AND ("class"."year" = 2009)
+                     AND ("class"."season" = 'spring')
+                     AND ("class"."section" = '001')
+               ORDER BY 1 ASC, 2 ASC, 3 ASC, 4 ASC, 5 ASC
           - uri: /program[ns]
             status: 400 Bad Request
             headers:
             headers:
             - [Content-Type, text/plain; charset=UTF-8]
             body: |
-              bind error: invalid integer literal: expected an integer in a decimal format; got 'uchem':
+              encode error: invalid integer literal: expected an integer in a decimal format; got 'uchem':
                   /course[ns.uchem]
                              ^^^^^
           - uri: /program^degree{id()}
               bind error: cannot determine identity:
                   /program^degree{id()}
                                   ^^^^
+          - uri: /course[$id]{id(), title} :where $id:='astro.105'
+            status: 400 Bad Request
+            headers:
+            - [Content-Type, text/plain; charset=UTF-8]
+            body: |
+              bind error: ill-formed locator:
+                  /course[$id]{id(), title} :where $id:='astro.105'
+                         ^^^^^
         - id: table-expressions
           tests:
           - uri: /(school?code='art').department
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.