Commits

Kirill Simonov committed 5d656da

Added API for making HTSQL requests from Python.

Comments (0)

Files changed (27)

src/htsql/__init__.py

 
 
 from . import (adapter, addon, application, cache, cmd, connect, context,
-               domain, entity, error, introspect, mark, request, split_sql,
+               domain, entity, error, introspect, mark, split_sql,
                tr, util, validator, wsgi)
 from .validator import DBVal
 from .addon import Addon, Parameter

src/htsql/application.py

 from .adapter import ComponentRegistry
 from .util import maybe, oneof, listof, dictof, tupleof
 from .wsgi import WSGI
+from .cmd.command import UniversalCmd
+from .cmd.act import produce
 
 
 class Application(object):
             for chunk in body:
                 yield chunk
 
+    def produce(self, uri, **parameters):
+        with self:
+            command = UniversalCmd(uri, parameters)
+            return produce(command)
 
+

src/htsql/cmd/act.py

 from ..tr.lookup import lookup_command
 from ..tr.parse import parse
 from ..tr.bind import bind
+from ..tr.embed import embed
 from ..fmt.format import FindRenderer
 
 
 
     def __call__(self):
         syntax = parse(self.command.query)
-        binding = bind(syntax)
+        environment = []
+        if self.command.parameters is not None:
+            for name in sorted(self.command.parameters):
+                value = self.command.parameters[name]
+                if isinstance(name, str):
+                    name = name.decode('utf-8')
+                recipe = embed(value)
+                environment.append((name, recipe))
+        binding = bind(syntax, environment=environment)
         command = lookup_command(binding)
         if command is None:
             command = DefaultCmd(binding)

src/htsql/cmd/command.py

 #
 
 
-from ..util import Printable
+from ..util import Printable, maybe, dictof, oneof
 from ..fmt import (TextRenderer, HTMLRenderer, JSONRenderer,
                    CSVRenderer, TSVRenderer)
 
 
 class UniversalCmd(Command):
 
-    def __init__(self, query):
+    def __init__(self, query, parameters=None):
         assert isinstance(query, str)
+        assert isinstance(parameters, maybe(dictof(oneof(str, unicode),
+                                                   object)))
         self.query = query
+        self.parameters = parameters
 
     def __str__(self):
         return repr(self.query)

src/htsql/cmd/retrieve.py

 
 
 from ..adapter import adapts, Utility
+from ..util import Record
 from .command import RetrieveCmd, SQLCmd
 from .act import (analyze, Act, ProduceAction, SafeProduceAction,
                   AnalyzeAction, RenderAction)
 from ..tr.assemble import assemble
 from ..tr.reduce import reduce
 from ..tr.dump import serialize
+from ..tr.lookup import guess_name
 from ..connect import DBError, Connect, Normalize
 from ..error import EngineError
 
 
     def __init__(self, binding):
         self.binding = binding
+        self.name = guess_name(binding)
+        if self.name is not None:
+            self.name = self.name.encode('utf-8')
         self.domain = binding.domain
         self.syntax = binding.syntax
         self.mark = binding.mark
 
     def __init__(self, binding):
         self.binding = binding
+        self.name = guess_name(binding)
+        if self.name is not None:
+            self.name = self.name.encode('utf-8')
         self.syntax = binding.syntax
         self.mark = binding.mark
         self.elements = [ElementProfile(element)
         profile = RequestProfile(plan)
         records = None
         if plan.sql:
-            select = plan.frame.segment.select
+            assert profile.segment is not None
             normalizers = []
-            for phrase in select:
-                normalize = Normalize(phrase.domain)
+            for element in profile.segment.elements:
+                normalize = Normalize(element.domain)
                 normalizers.append(normalize())
+            record_name = profile.segment.name
+            element_names = [element.name
+                             for element in profile.segment.elements]
+            record_class = Record.make(record_name, element_names)
             connection = None
             try:
                 connect = Connect()
                     for item, normalize in zip(row, normalizers):
                         value = normalize(item)
                         values.append(value)
-                    records.append((values))
+                    records.append(record_class(*values))
                 connection.commit()
                 connection.release()
             except DBError, exc:

src/htsql/connect.py

             if not rows:
                 return rows
             fields = [kind[0].lower() for kind in self.description]
-        Row = Record.make(fields)
+        Row = Record.make(None, fields)
         return [Row(*row) for row in rows]
 
     def __iter__(self):

src/htsql/request.py

-#
-# Copyright (c) 2006-2011, Prometheus Research, LLC
-# See `LICENSE` for license information, `AUTHORS` for the list of authors.
-#
-
-
-"""
-:mod:`htsql.request`
-====================
-
-This module implements the request utility.
-"""
-
-
-from .adapter import Utility, Realization
-from .cmd.act import produce as produce_cmd, render as render_cmd
-from .cmd.command import UniversalCmd
-import urllib
-
-
-class Request(Utility):
-
-    @classmethod
-    def build(cls, environ):
-        # FIXME: override `classmethod` in `htsql.adapter`?
-        if not issubclass(cls, Realization):
-            cls = cls.realize(())
-            return cls.build(environ)
-        path_info = environ['PATH_INFO']
-        query_string = environ.get('QUERY_STRING')
-        uri = urllib.quote(path_info)
-        if query_string:
-            uri += '?'+query_string
-        return cls(uri)
-
-    def __init__(self, uri):
-        self.uri = uri
-
-    def produce(self):
-        command = UniversalCmd(self.uri)
-        return produce_cmd(command)
-
-    def render(self, environ):
-        command = UniversalCmd(self.uri)
-        return render_cmd(command, environ)
-
-    def __call__(self, environ):
-        return self.render(environ)
-
-
-def render(environ):
-    request = Request.build(environ)
-    return request.render(environ)
-
-
-def produce(uri):
-    request = Request(uri)
-    return request.produce()
-
-

src/htsql/tr/__init__.py

 """
 
 
-from . import (assemble, binding, bind, coerce, compile, dump, encode, error,
-               flow, fn, frame, lookup, parse, plan, reduce, rewrite, scan,
-               signature, stitch, syntax, term, token)
+from . import (assemble, binding, bind, coerce, compile, dump, encode, embed,
+               error, flow, fn, frame, lookup, parse, plan, reduce, rewrite,
+               scan, signature, stitch, syntax, term, token)
 
 

src/htsql/tr/bind.py

 from ..adapter import Adapter, Protocol, adapts
 from ..domain import (BooleanDomain, IntegerDomain, DecimalDomain,
                       FloatDomain, UntypedDomain)
-from ..classify import relabel
+from ..classify import relabel, normalize
 from .error import BindError
 from .syntax import (Syntax, QuerySyntax, SegmentSyntax, SelectorSyntax,
                      ApplicationSyntax, FunctionSyntax, MappingSyntax,
                       AssignmentBinding, DefinitionBinding, SelectionBinding,
                       RerouteBinding, ReferenceRerouteBinding, AliasBinding,
                       LiteralBinding,
-                      Recipe, FreeTableRecipe, AttachedTableRecipe,
+                      Recipe, LiteralRecipe, SelectionRecipe,
+                      FreeTableRecipe, AttachedTableRecipe,
                       ColumnRecipe, KernelRecipe, ComplementRecipe,
                       SubstitutionRecipe, BindingRecipe, ClosedRecipe,
                       PinnedRecipe, AmbiguousRecipe)
         The current naming scope.
     """
 
-    def __init__(self):
+    def __init__(self, environment=None):
         # The root lookup scope.
         self.root = None
         # The current lookup scope.
         self.scope = None
         # The stack of previous lookup scopes.
         self.scope_stack = []
+        # References in the root scope.
+        self.environment = environment
 
     def set_root(self, root):
         """
         assert isinstance(root, RootBinding)
         self.root = root
         self.scope = root
+        # Add global references.
+        if self.environment is not None:
+            for name, recipe in self.environment:
+                name = normalize(name)
+                self.scope = DefinitionBinding(self.scope, name, True, None,
+                                               recipe, self.scope.syntax)
 
     def flush(self):
         """
         # scope to coincide with the root scope.
         assert self.root is not None
         assert not self.scope_stack
-        assert self.root is self.scope
+        #assert self.root is self.scope
         self.root = None
         self.scope = None
 
         raise BindError("unable to bind a node", self.syntax.mark)
 
 
+class BindByLiteral(BindByRecipe):
+
+    adapts(LiteralRecipe)
+
+    def __call__(self):
+        return LiteralBinding(self.state.scope,
+                              self.recipe.value,
+                              self.recipe.domain,
+                              self.syntax)
+
+
+class BindBySelection(BindByRecipe):
+
+    adapts(SelectionRecipe)
+
+    def __call__(self):
+        elements = []
+        for recipe in self.recipe.recipes:
+            element = self.state.use(recipe, self.syntax)
+            elements.append(element)
+        return SelectionBinding(self.state.scope, elements, self.syntax)
+
+
 class BindByFreeTable(BindByRecipe):
 
     adapts(FreeTableRecipe)
                         self.syntax.mark, hint=hint)
 
 
-def bind(syntax, state=None, scope=None):
+def bind(syntax, state=None, scope=None, environment=None):
     """
     Binds the given syntax node.
 
     """
     # Create a new binding state if necessary.
     if state is None:
-        state = BindingState()
+        state = BindingState(environment)
     # If passed, set the new lookup scope.
     if scope is not None:
         state.push_scope(scope)

src/htsql/tr/binding.py

                                              base, domain, syntax)
 
 
+class LiteralRecipe(Recipe):
+
+    def __init__(self, value, domain):
+        assert isinstance(domain, Domain)
+        super(LiteralRecipe, self).__init__(equality_vector=(value, domain))
+        self.value = value
+        self.domain = domain
+
+    def __str__(self):
+        return "%s: %s" % (self.value, self.domain)
+
+
+class SelectionRecipe(Recipe):
+
+    def __init__(self, recipes):
+        assert isinstance(recipes, listof(Recipe))
+        super(SelectionRecipe, self).__init__(
+            equality_vector=(tuple(recipes),))
+        self.recipes = recipes
+
+    def __str__(self):
+        return "{%s}" % ",".join(str(recipe) for recipe in self.recipes)
+
+
 class FreeTableRecipe(Recipe):
     """
     Generates a :class:`FreeTableBinding` node.

src/htsql/tr/embed.py

+#
+# Copyright (c) 2006-2011, Prometheus Research, LLC
+# See `LICENSE` for license information, `AUTHORS` for the list of authors.
+#
+
+
+from ..adapter import Adapter, adapts, adapts_many
+from ..domain import (UntypedDomain, BooleanDomain, IntegerDomain, FloatDomain,
+                      DecimalDomain, DateDomain, TimeDomain, DateTimeDomain)
+from .binding import LiteralRecipe, SelectionRecipe
+from .coerce import coerce
+import types
+import datetime
+import decimal
+
+
+class Embed(Adapter):
+
+    adapts(object)
+
+    def __init__(self, value):
+        self.value = value
+
+    def __call__(self):
+        raise ValueError("unable to embed a value of type %s"
+                         % type(self.value))
+
+
+class EmbedUntyped(Embed):
+
+    adapts_many(str, unicode)
+
+    def __call__(self):
+        value = self.value
+        if isinstance(value, str):
+            try:
+                value = value.decode('utf-8')
+            except UnicodeDecodeError, exc:
+                raise ValueError("a string is expected to be encoded in UTF-8:"
+                                 " %s" % repr(value))
+        if u"\0" in value:
+            raise ValueError("a string should not contain a NIL character:"
+                             " %s" % repr(value))
+        return LiteralRecipe(value, UntypedDomain())
+
+
+class EmbedNull(Embed):
+
+    adapts(types.NoneType)
+
+    def __call__(self):
+        return LiteralRecipe(None, UntypedDomain())
+
+
+class EmbedBoolean(Embed):
+
+    adapts(bool)
+
+    def __call__(self):
+        return LiteralRecipe(self.value, BooleanDomain())
+
+
+class EmbedInteger(Embed):
+
+    adapts_many(int, long)
+
+    def __call__(self):
+        return LiteralRecipe(self.value, IntegerDomain())
+
+
+class EmbedFloat(Embed):
+
+    adapts(float)
+
+    def __call__(self):
+        return LiteralRecipe(self.value, FloatDomain())
+
+
+class EmbedDecimal(Embed):
+
+    adapts(decimal.Decimal)
+
+    def __call__(self):
+        return LiteralRecipe(self.value, DecimalDomain())
+
+
+class EmbedDate(Embed):
+
+    adapts(datetime.date)
+
+    def __call__(self):
+        return LiteralRecipe(self.value, DateDomain())
+
+
+class EmbedTime(Embed):
+
+    adapts(datetime.time)
+
+    def __call__(self):
+        return LiteralRecipe(self.value, TimeDomain())
+
+
+class EmbedDateTime(Embed):
+
+    adapts(datetime.datetime)
+
+    def __call__(self):
+        return LiteralRecipe(self.value, DateTimeDomain())
+
+
+class EmbedList(Embed):
+
+    adapts_many(list, tuple)
+
+    def __call__(self):
+        recipes = []
+        for value in self.value:
+            recipe = embed(value)
+            recipes.append(recipe)
+        return SelectionRecipe(recipes)
+
+
+def embed(value):
+    embed = Embed(value)
+    return embed()
+
+

src/htsql/tr/lookup.py

         return lookup(self.binding.base, self.probe)
 
 
+class GuessNameFromSegment(Lookup):
+    # Generate an attribute name from a segment binding.
+
+    adapts(SegmentBinding, GuessNameProbe)
+
+    def __call__(self):
+        # If available, use the name of the segment seed.
+        if self.binding.seed is not None:
+            return lookup(self.binding.seed, self.probe)
+        # Otherwise, fail to generate a name.
+        return None
+
+
 class GuessTitleFromSegment(Lookup):
     # Generate a heading from a segment binding.
 

src/htsql/util.py

     __fields__ = ()
 
     @classmethod
-    def make(cls, fields):
+    def make(cls, name, fields):
+        assert isinstance(name, maybe(str))
         assert isinstance(fields, listof(maybe(str)))
+        if name is not None and not re.match(r'^(?!\d)\w+$', name):
+            name = None
+        if name is not None and keyword.iskeyword(name):
+            name = name+'_'
+        if name is None:
+            name = cls.__name__
         duplicates = set()
         for idx, field in enumerate(fields):
             if field is None:
             if field is None:
                 continue
             attributes[field] = property(operator.itemgetter(idx))
-        return type(cls.__name__, bases, attributes)
+        return type(name, bases, attributes)
 
     def __new__(cls, *args, **kwds):
         if kwds:
     def __repr__(self):
         return ("%s(%s)"
                 % (self.__class__.__name__,
-                   ", ".join("%s=%r" % (name or '?', value)
-                             for name, value in zip(self.__fields__, self))))
+                   ", ".join("%s=%r" % (name or '[%s]' % idx, value)
+                             for idx, (name, value)
+                                in enumerate(zip(self.__fields__, self)))))
 
 
 class Printable(object):

src/htsql/wsgi.py

 """
 
 from .adapter import Utility
-from .request import Request
 from .error import HTTPError
+from .cmd.command import UniversalCmd
+from .cmd.act import render
+import urllib
 
 
 class WSGI(Utility):
         body = wsgi(environ, start_response)
     """
 
+    def request(self, environ):
+        """
+        Extracts HTSQL request from `environ`.
+        """
+        path_info = environ['PATH_INFO']
+        query_string = environ.get('QUERY_STRING')
+        uri = urllib.quote(path_info)
+        if query_string:
+            uri += '?'+query_string
+        return uri
+
     def __call__(self, environ, start_response):
         """
         Implements the WSGI entry point.
             start_response('400 Bad Request', [('Content-Type', 'text/plain')])
             return ["%s requests are not permitted.\n" % method]
         # Process the query.
-        request = Request.build(environ)
+        uri = self.request(environ)
         try:
-            status, headers, body = request.render(environ)
+            command = UniversalCmd(uri)
+            status, headers, body = render(command, environ)
         except HTTPError, exc:
             return exc(environ, start_response)
         start_response(status, headers)

src/htsql_tweak/meta/command.py

 
 class MetaCmd(ProducerCmd):
 
-    def __init__(self, syntax):
+    def __init__(self, syntax, environment=None):
         self.syntax = syntax
+        self.environment = environment
 
 
 class BindMeta(BindCommand):
         if not isinstance(op, SegmentSyntax):
             raise BindError("a segment is required", op.mark)
         op = QuerySyntax(op, op.mark)
-        command = MetaCmd(op)
+        command = MetaCmd(op, environment=self.state.environment)
         return CommandBinding(self.state.scope, command, self.syntax)
 
 
     def __call__(self):
         slave_app = get_slave_app()
         with slave_app:
-            binding = bind(self.command.syntax)
+            binding = bind(self.command.syntax,
+                           environment=self.command.environment)
             command = lookup_command(binding)
             if command is None:
                 command = DefaultCmd(binding)

test/code/test_embedding.py

+
+from htsql import HTSQL
+import decimal, datetime
+
+db = str(state.app.htsql.db)
+
+htsql = HTSQL(db)
+
+uri = "/school{code, count(program), count(department)}"
+print "URI:", uri
+for row in htsql.produce(uri):
+    print row
+print
+
+uri = "/school{name, count(program)}?code=$school_code"
+school_code = "bus"
+print "URI:", uri
+print "$school_code: %r" % school_code
+for row in htsql.produce(uri, school_code=school_code):
+    print "%s: %s" % row
+print
+
+uri = "/school{name, num_prog:=count(program)}" \
+      "?num_prog>=$min_prog&num_prog<=$max_prog"
+min_prog = 6
+max_prog = 8
+print "URI:", uri
+print "$min_prog: %r" % min_prog
+print "$max_prog: %r" % max_prog
+for row in htsql.produce(uri, min_prog=min_prog, max_prog=max_prog):
+    print "%s: %s" % (row.name, row.num_prog)
+print
+
+uri = "/school?campus==$campus"
+campus = None
+print "URI:", uri
+print "$campus: %r" % campus
+for row in htsql.produce(uri, campus=campus):
+    print row
+print
+
+uri = "/school?campus=$campus"
+campus = ['north', 'south']
+print "URI:", uri
+print "$campus: %r" % campus
+for row in htsql.produce(uri, campus=campus):
+    print row
+print
+
+uri = "/{$untyped, $selector, $boolean, $integer, $float, $decimal," \
+      " $date, $time, $datetime}"
+untyped_value = "HTSQL"
+selector_value = ["HTTP", "SQL"]
+boolean_value = True
+integer_value = 3571
+float_value = -57721e-5
+decimal_value = decimal.Decimal("0.875")
+if 'sqlite' in state.toggles:
+    decimal_value = None
+date_value = datetime.date(2010, 4, 15)
+time_value = datetime.time(20, 3)
+datetime_value = datetime.datetime(2010, 4, 15, 20, 3)
+print "URI:", uri
+print "$untyped: %r" % untyped_value
+print "$selector: %r" % selector_value
+print "$boolean: %r" % boolean_value
+print "$integer: %r" % integer_value
+print "$float: %r" % float_value
+print "$decimal: %r" % decimal_value
+print "$date: %r" % date_value
+print "$time: %r" % time_value
+print "$datetime: %r" % datetime_value
+for row in htsql.produce(uri, untyped=untyped_value,
+                              selector=selector_value,
+                              boolean=boolean_value,
+                              integer=integer_value,
+                              float=float_value,
+                              decimal=decimal_value,
+                              date=date_value,
+                              time=time_value,
+                              datetime=datetime_value):
+    print row
+print
+
+htsql = HTSQL(db, {'tweak.meta': None})
+
+uri = "/meta(/link?table.name=$table_name)"
+table_name = "school"
+print "URI:", uri
+print "$table_name: %r" % table_name
+for row in htsql.produce(uri, table_name=table_name):
+    print row
+print
+
+htsql = HTSQL(None, {'htsql': {'db': db}, 'tweak.autolimit': {'limit': 3}})
+
+uri = "/school"
+print "URI:", uri
+for row in htsql.produce(uri):
+    print row
+

test/input/embedding.yaml

+#
+# Copyright (c) 2006-2011, Prometheus Research, LLC
+# See `LICENSE` for license information, `AUTHORS` for the list of authors.
+#
+
+title: Embedding HTSQL
+id: embedding
+tests:
+- py-include: test/code/test_embedding.py
+

test/input/mssql.yaml

   - include: test/input/addon.yaml
   # Error Reporting
   - include: test/input/error.yaml
+  # Embedding HTSQL
+  - include: test/input/embedding.yaml
 

test/input/mysql.yaml

   - include: test/input/addon.yaml
   # Error Reporting
   - include: test/input/error.yaml
+  # Embedding HTSQL
+  - include: test/input/embedding.yaml
 

test/input/oracle.yaml

   - include: test/input/addon.yaml
   # Error Reporting
   - include: test/input/error.yaml
+  # Embedding HTSQL
+  - include: test/input/embedding.yaml
 

test/input/pgsql.yaml

   - include: test/input/addon.yaml
   # Error Reporting
   - include: test/input/error.yaml
+  # Embedding HTSQL
+  - include: test/input/embedding.yaml
 

test/input/sqlite.yaml

   - include: test/input/addon.yaml
   # Error Reporting
   - include: test/input/error.yaml
+  # Embedding HTSQL
+  - include: test/input/embedding.yaml
 

test/output/mssql.yaml

             serialize error: invalid integer value:
                 /18446744073709551616
                  ^^^^^^^^^^^^^^^^^^^^
+  - include: test/input/embedding.yaml
+    output:
+      id: embedding
+      tests:
+      - py-include: test/code/test_embedding.py
+        stdout: 'URI: /school{code, count(program), count(department)}
+
+          school(code=u''art'', [1]=3, [2]=2)
+
+          school(code=u''bus'', [1]=6, [2]=3)
+
+          school(code=u''edu'', [1]=7, [2]=2)
+
+          school(code=u''eng'', [1]=8, [2]=4)
+
+          school(code=u''la'', [1]=9, [2]=5)
+
+          school(code=u''mus'', [1]=0, [2]=4)
+
+          school(code=u''ns'', [1]=6, [2]=4)
+
+          school(code=u''ph'', [1]=1, [2]=0)
+
+          school(code=u''sc'', [1]=0, [2]=0)
+
+
+          URI: /school{name, count(program)}?code=$school_code
+
+          $school_code: ''bus''
+
+          School of Business: 6
+
+
+          URI: /school{name, num_prog:=count(program)}?num_prog>=$min_prog&num_prog<=$max_prog
+
+          $min_prog: 6
+
+          $max_prog: 8
+
+          School of Business: 6
+
+          College of Education: 7
+
+          School of Engineering: 8
+
+          School of Natural Sciences: 6
+
+
+          URI: /school?campus==$campus
+
+          $campus: None
+
+          school(code=u''ph'', name=u''Public Honorariums'', campus=None)
+
+          school(code=u''sc'', name=u''School of Continuing Studies'', campus=None)
+
+
+          URI: /school?campus=$campus
+
+          $campus: [''north'', ''south'']
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''eng'', name=u''School of Engineering'', campus=u''north'')
+
+          school(code=u''mus'', name=u''School of Music & Dance'', campus=u''south'')
+
+
+          URI: /{$untyped, $selector, $boolean, $integer, $float, $decimal, $date,
+          $time, $datetime}
+
+          $untyped: ''HTSQL''
+
+          $selector: [''HTTP'', ''SQL'']
+
+          $boolean: True
+
+          $integer: 3571
+
+          $float: -0.57721
+
+          $decimal: Decimal(''0.875'')
+
+          $date: datetime.date(2010, 4, 15)
+
+          $time: datetime.time(20, 3)
+
+          $datetime: datetime.datetime(2010, 4, 15, 20, 3)
+
+          Record([0]=u''HTSQL'', [1]=u''HTTP'', [2]=u''SQL'', [3]=True, [4]=3571,
+          [5]=-0.57721, [6]=Decimal(''0.875''), [7]=datetime.date(2010, 4, 15), [8]=datetime.time(20,
+          3), [9]=datetime.datetime(2010, 4, 15, 20, 3))
+
+
+          URI: /meta(/link?table.name=$table_name)
+
+          $table_name: ''school''
+
+          link(table_name=u''school'', name=u''department'', is_singular=False, target_name=u''department'',
+          reverse_name=u''school'')
+
+          link(table_name=u''school'', name=u''program'', is_singular=False, target_name=u''program'',
+          reverse_name=u''school'')
+
+
+          URI: /school
+
+          school(code=u''art'', name=u''School of Art and Design'', campus=u''old'')
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''edu'', name=u''College of Education'', campus=u''old'')
+
+'

test/output/mysql.yaml

             serialize error: integer value is out of range:
                 /18446744073709551616
                  ^^^^^^^^^^^^^^^^^^^^
+  - include: test/input/embedding.yaml
+    output:
+      id: embedding
+      tests:
+      - py-include: test/code/test_embedding.py
+        stdout: 'URI: /school{code, count(program), count(department)}
+
+          school(code=u''art'', [1]=3L, [2]=2L)
+
+          school(code=u''bus'', [1]=6L, [2]=3L)
+
+          school(code=u''edu'', [1]=7L, [2]=2L)
+
+          school(code=u''eng'', [1]=8L, [2]=4L)
+
+          school(code=u''la'', [1]=9L, [2]=5L)
+
+          school(code=u''mus'', [1]=0L, [2]=4L)
+
+          school(code=u''ns'', [1]=6L, [2]=4L)
+
+          school(code=u''ph'', [1]=1L, [2]=0L)
+
+          school(code=u''sc'', [1]=0L, [2]=0L)
+
+
+          URI: /school{name, count(program)}?code=$school_code
+
+          $school_code: ''bus''
+
+          School of Business: 6
+
+
+          URI: /school{name, num_prog:=count(program)}?num_prog>=$min_prog&num_prog<=$max_prog
+
+          $min_prog: 6
+
+          $max_prog: 8
+
+          School of Business: 6
+
+          College of Education: 7
+
+          School of Engineering: 8
+
+          School of Natural Sciences: 6
+
+
+          URI: /school?campus==$campus
+
+          $campus: None
+
+          school(code=u''ph'', name=u''Public Honorariums'', campus=None)
+
+          school(code=u''sc'', name=u''School of Continuing Studies'', campus=None)
+
+
+          URI: /school?campus=$campus
+
+          $campus: [''north'', ''south'']
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''eng'', name=u''School of Engineering'', campus=u''north'')
+
+          school(code=u''mus'', name=u''School of Music & Dance'', campus=u''south'')
+
+
+          URI: /{$untyped, $selector, $boolean, $integer, $float, $decimal, $date,
+          $time, $datetime}
+
+          $untyped: ''HTSQL''
+
+          $selector: [''HTTP'', ''SQL'']
+
+          $boolean: True
+
+          $integer: 3571
+
+          $float: -0.57721
+
+          $decimal: Decimal(''0.875'')
+
+          $date: datetime.date(2010, 4, 15)
+
+          $time: datetime.time(20, 3)
+
+          $datetime: datetime.datetime(2010, 4, 15, 20, 3)
+
+          Record([0]=u''HTSQL'', [1]=u''HTTP'', [2]=u''SQL'', [3]=True, [4]=3571L,
+          [5]=-0.57721, [6]=Decimal(''0.875''), [7]=datetime.date(2010, 4, 15), [8]=datetime.time(20,
+          3), [9]=datetime.datetime(2010, 4, 15, 20, 3))
+
+
+          URI: /meta(/link?table.name=$table_name)
+
+          $table_name: ''school''
+
+          link(table_name=u''school'', name=u''department'', is_singular=False, target_name=u''department'',
+          reverse_name=u''school'')
+
+          link(table_name=u''school'', name=u''program'', is_singular=False, target_name=u''program'',
+          reverse_name=u''school'')
+
+
+          URI: /school
+
+          school(code=u''art'', name=u''School of Art and Design'', campus=u''old'')
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''edu'', name=u''College of Education'', campus=u''old'')
+
+'

test/output/oracle.yaml

             compile error: a singular expression is expected:
                 /school{department^count(course){*}}
                                                  ^
+  - include: test/input/embedding.yaml
+    output:
+      id: embedding
+      tests:
+      - py-include: test/code/test_embedding.py
+        stdout: 'URI: /school{code, count(program), count(department)}
+
+          school(code=u''art'', [1]=3, [2]=2)
+
+          school(code=u''bus'', [1]=6, [2]=3)
+
+          school(code=u''edu'', [1]=7, [2]=2)
+
+          school(code=u''eng'', [1]=8, [2]=4)
+
+          school(code=u''la'', [1]=9, [2]=5)
+
+          school(code=u''mus'', [1]=0, [2]=4)
+
+          school(code=u''ns'', [1]=6, [2]=4)
+
+          school(code=u''ph'', [1]=1, [2]=0)
+
+          school(code=u''sc'', [1]=0, [2]=0)
+
+
+          URI: /school{name, count(program)}?code=$school_code
+
+          $school_code: ''bus''
+
+          School of Business: 6
+
+
+          URI: /school{name, num_prog:=count(program)}?num_prog>=$min_prog&num_prog<=$max_prog
+
+          $min_prog: 6
+
+          $max_prog: 8
+
+          School of Business: 6
+
+          College of Education: 7
+
+          School of Engineering: 8
+
+          School of Natural Sciences: 6
+
+
+          URI: /school?campus==$campus
+
+          $campus: None
+
+          school(code=u''ph'', name=u''Public Honorariums'', campus=None)
+
+          school(code=u''sc'', name=u''School of Continuing Studies'', campus=None)
+
+
+          URI: /school?campus=$campus
+
+          $campus: [''north'', ''south'']
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''eng'', name=u''School of Engineering'', campus=u''north'')
+
+          school(code=u''mus'', name=u''School of Music & Dance'', campus=u''south'')
+
+
+          URI: /{$untyped, $selector, $boolean, $integer, $float, $decimal, $date,
+          $time, $datetime}
+
+          $untyped: ''HTSQL''
+
+          $selector: [''HTTP'', ''SQL'']
+
+          $boolean: True
+
+          $integer: 3571
+
+          $float: -0.57721
+
+          $decimal: Decimal(''0.875'')
+
+          $date: datetime.date(2010, 4, 15)
+
+          $time: datetime.time(20, 3)
+
+          $datetime: datetime.datetime(2010, 4, 15, 20, 3)
+
+          Record([0]=u''HTSQL'', [1]=u''HTTP'', [2]=u''SQL'', [3]=True, [4]=3571,
+          [5]=-0.57721, [6]=Decimal(''0.875''), [7]=datetime.date(2010, 4, 15), [8]=datetime.time(20,
+          3), [9]=datetime.datetime(2010, 4, 15, 20, 3))
+
+
+          URI: /meta(/link?table.name=$table_name)
+
+          $table_name: ''school''
+
+          link(table_name=u''school'', name=u''department'', is_singular=False, target_name=u''department'',
+          reverse_name=u''school'')
+
+          link(table_name=u''school'', name=u''program'', is_singular=False, target_name=u''program'',
+          reverse_name=u''school'')
+
+
+          URI: /school
+
+          school(code=u''art'', name=u''School of Art and Design'', campus=u''old'')
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''edu'', name=u''College of Education'', campus=u''old'')
+
+'

test/output/pgsql.yaml

             serialize error: integer value is out of range:
                 /18446744073709551616
                  ^^^^^^^^^^^^^^^^^^^^
+  - include: test/input/embedding.yaml
+    output:
+      id: embedding
+      tests:
+      - py-include: test/code/test_embedding.py
+        stdout: 'URI: /school{code, count(program), count(department)}
+
+          school(code=u''art'', [1]=3L, [2]=2L)
+
+          school(code=u''bus'', [1]=6L, [2]=3L)
+
+          school(code=u''edu'', [1]=7L, [2]=2L)
+
+          school(code=u''eng'', [1]=8L, [2]=4L)
+
+          school(code=u''la'', [1]=9L, [2]=5L)
+
+          school(code=u''mus'', [1]=0L, [2]=4L)
+
+          school(code=u''ns'', [1]=6L, [2]=4L)
+
+          school(code=u''ph'', [1]=1L, [2]=0L)
+
+          school(code=u''sc'', [1]=0L, [2]=0L)
+
+
+          URI: /school{name, count(program)}?code=$school_code
+
+          $school_code: ''bus''
+
+          School of Business: 6
+
+
+          URI: /school{name, num_prog:=count(program)}?num_prog>=$min_prog&num_prog<=$max_prog
+
+          $min_prog: 6
+
+          $max_prog: 8
+
+          School of Business: 6
+
+          College of Education: 7
+
+          School of Engineering: 8
+
+          School of Natural Sciences: 6
+
+
+          URI: /school?campus==$campus
+
+          $campus: None
+
+          school(code=u''ph'', name=u''Public Honorariums'', campus=None)
+
+          school(code=u''sc'', name=u''School of Continuing Studies'', campus=None)
+
+
+          URI: /school?campus=$campus
+
+          $campus: [''north'', ''south'']
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''eng'', name=u''School of Engineering'', campus=u''north'')
+
+          school(code=u''mus'', name=u''School of Music & Dance'', campus=u''south'')
+
+
+          URI: /{$untyped, $selector, $boolean, $integer, $float, $decimal, $date,
+          $time, $datetime}
+
+          $untyped: ''HTSQL''
+
+          $selector: [''HTTP'', ''SQL'']
+
+          $boolean: True
+
+          $integer: 3571
+
+          $float: -0.57721
+
+          $decimal: Decimal(''0.875'')
+
+          $date: datetime.date(2010, 4, 15)
+
+          $time: datetime.time(20, 3)
+
+          $datetime: datetime.datetime(2010, 4, 15, 20, 3)
+
+          Record([0]=u''HTSQL'', [1]=u''HTTP'', [2]=u''SQL'', [3]=True, [4]=3571,
+          [5]=-0.57721, [6]=Decimal(''0.875''), [7]=datetime.date(2010, 4, 15), [8]=datetime.time(20,
+          3), [9]=datetime.datetime(2010, 4, 15, 20, 3))
+
+
+          URI: /meta(/link?table.name=$table_name)
+
+          $table_name: ''school''
+
+          link(table_name=u''school'', name=u''department'', is_singular=False, target_name=u''department'',
+          reverse_name=u''school'')
+
+          link(table_name=u''school'', name=u''program'', is_singular=False, target_name=u''program'',
+          reverse_name=u''school'')
+
+
+          URI: /school
+
+          school(code=u''art'', name=u''School of Art and Design'', campus=u''old'')
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''edu'', name=u''College of Education'', campus=u''old'')
+
+'

test/output/sqlite.yaml

             serialize error: integer value is out of range:
                 /18446744073709551616
                  ^^^^^^^^^^^^^^^^^^^^
+  - include: test/input/embedding.yaml
+    output:
+      id: embedding
+      tests:
+      - py-include: test/code/test_embedding.py
+        stdout: 'URI: /school{code, count(program), count(department)}
+
+          school(code=u''art'', [1]=3, [2]=2)
+
+          school(code=u''bus'', [1]=6, [2]=3)
+
+          school(code=u''edu'', [1]=7, [2]=2)
+
+          school(code=u''eng'', [1]=8, [2]=4)
+
+          school(code=u''la'', [1]=9, [2]=5)
+
+          school(code=u''mus'', [1]=0, [2]=4)
+
+          school(code=u''ns'', [1]=6, [2]=4)
+
+          school(code=u''ph'', [1]=1, [2]=0)
+
+          school(code=u''sc'', [1]=0, [2]=0)
+
+
+          URI: /school{name, count(program)}?code=$school_code
+
+          $school_code: ''bus''
+
+          School of Business: 6
+
+
+          URI: /school{name, num_prog:=count(program)}?num_prog>=$min_prog&num_prog<=$max_prog
+
+          $min_prog: 6
+
+          $max_prog: 8
+
+          School of Business: 6
+
+          College of Education: 7
+
+          School of Engineering: 8
+
+          School of Natural Sciences: 6
+
+
+          URI: /school?campus==$campus
+
+          $campus: None
+
+          school(code=u''ph'', name=u''Public Honorariums'', campus=None)
+
+          school(code=u''sc'', name=u''School of Continuing Studies'', campus=None)
+
+
+          URI: /school?campus=$campus
+
+          $campus: [''north'', ''south'']
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''eng'', name=u''School of Engineering'', campus=u''north'')
+
+          school(code=u''mus'', name=u''School of Music & Dance'', campus=u''south'')
+
+
+          URI: /{$untyped, $selector, $boolean, $integer, $float, $decimal, $date,
+          $time, $datetime}
+
+          $untyped: ''HTSQL''
+
+          $selector: [''HTTP'', ''SQL'']
+
+          $boolean: True
+
+          $integer: 3571
+
+          $float: -0.57721
+
+          $decimal: None
+
+          $date: datetime.date(2010, 4, 15)
+
+          $time: datetime.time(20, 3)
+
+          $datetime: datetime.datetime(2010, 4, 15, 20, 3)
+
+          Record([0]=u''HTSQL'', [1]=u''HTTP'', [2]=u''SQL'', [3]=True, [4]=3571,
+          [5]=-0.57721, [6]=None, [7]=datetime.date(2010, 4, 15), [8]=datetime.time(20,
+          3), [9]=datetime.datetime(2010, 4, 15, 20, 3))
+
+
+          URI: /meta(/link?table.name=$table_name)
+
+          $table_name: ''school''
+
+          link(table_name=u''school'', name=u''department'', is_singular=False, target_name=u''department'',
+          reverse_name=u''school'')
+
+          link(table_name=u''school'', name=u''program'', is_singular=False, target_name=u''program'',
+          reverse_name=u''school'')
+
+
+          URI: /school
+
+          school(code=u''art'', name=u''School of Art and Design'', campus=u''old'')
+
+          school(code=u''bus'', name=u''School of Business'', campus=u''south'')
+
+          school(code=u''edu'', name=u''College of Education'', campus=u''old'')
+
+'