Commits

Kirill Simonov committed a05590b

Updated tweak.sqlalchemy, added tests.

Comments (0)

Files changed (16)

src/htsql_ctl/regress.py

 
     class Input(TestData):
         fields = [
-                Field('db', DBVal(),
+                Field('db', DBVal(is_nullable=True),
                       hint="""the connection URI"""),
                 Field('extensions', MapVal(StrVal(),
                                            MapVal(StrVal(), AnyVal())),
 
         # Clone `input.db`, but omit the password.
         db = self.input.db
-        sanitized_db = DB(engine=db.engine,
-                          username=db.username,
-                          password=None,
-                          host=db.host,
-                          port=db.port,
-                          database=db.database,
-                          options=db.options)
+        if db is not None:
+            sanitized_db = DB(engine=db.engine,
+                              username=db.username,
+                              password=None,
+                              host=db.host,
+                              port=db.port,
+                              database=db.database,
+                              options=db.options)
+        else:
+            sanitized_db = "-"
 
         # Print:
         # ---------------- ... -
         # Create an application and update the testing state.  The created
         # application will be in effect for the subsequent tests in the
         # current suite and all the nested suites unless overridden.
-        from htsql.application import Application
+        from htsql import HTSQL
         self.state.app = None
         try:
-            self.state.app = Application(self.input.db,
-                                         self.input.extensions)
+            self.state.app = HTSQL(self.input.db,
+                                   self.input.extensions)
         except Exception:
             self.out_exception(sys.exc_info())
             return self.failed("*** an exception occured while"
         configuration = configuration+(self.input.extensions,)
 
         # Create an application and update the testing state.
-        from htsql.application import Application
+        from htsql import HTSQL
         self.state.app = None
         try:
-            self.state.app = Application(*configuration)
+            self.state.app = HTSQL(*configuration)
         except Exception:
             self.out_exception(sys.exc_info())
             return self.failed("*** an exception occured while"
         # Generate an HTSQL application.  We need an application instance
         # to split the SQL data and to connect to the database, but we
         # never use it for executing HTSQL queries.
-        from htsql.application import Application
+        from htsql import HTSQL
         from htsql.connect import connect, DBError
         from htsql.split_sql import SplitSQL
         try:
-            app = Application(self.input.connect)
+            app = HTSQL(self.input.connect)
         except Exception, exc:
             self.out_exception(sys.exc_info())
             return self.failed("*** an exception occured while"

src/htsql_tweak/override/introspect.py

 
 class OverrideIntrospect(Introspect):
 
-    weigh(1.0)
+    weigh(2.0)
 
     def __call__(self):
         addon = context.app.tweak.override

src/htsql_tweak/sqlalchemy/__init__.py

     prerequisites = []
     postrequisites = ['htsql']
     name = 'tweak.sqlalchemy'
-    hint = """adapts to SQLAlchemy engine and model"""
+    hint = """use SQLAlchemy engine and model"""
     help = """
-      This plugin provides SQLAlchemy integration in two ways.
-      First, if the dburi is omitted, it attempts to use the
-      database connection from SQLAlchemy.  Secondly, it uses
-      the SQLAlchemy model instead of introspecting.
+    This addon provides SQLAlchemy integration in two ways.
+    First, it permits using database connections from SQLAlchemy.
+    Second, it uses SQLAlchemy model instead of introspecting
+    the database.
+
+    Parameter `engine` and `metadata` must point to the SQLAlchemy
+    `engine` and `metadata` objects.  The objects are specified
+    by a dotted string of module names.  The last component in
+    the dotted string is a module attribute.
     """
 
     parameters = [
             Parameter('engine', ClassVal(SQLAlchemyEngine),
-              hint='the SQLAlchemy ``engine`` object',
-              value_name='package.module.attribute'),
+              value_name="MODULE.NAME",
+              hint='the SQLAlchemy `engine` object'),
             Parameter('metadata', ClassVal(SQLAlchemyMetaData),
-              hint='the SQLAlchemy ``metadata`` object',
-              value_name='package.module.attribute')
+              value_name="MODULE.NAME",
+              hint='the SQLAlchemy `metadata` object')
     ]
 
     @classmethod
         if sqlalchemy_engine:
             assert isinstance(sqlalchemy_engine, SQLAlchemyEngine)
             engine = sqlalchemy_engine.dialect.name
+            engine = {
+                    'postgresql': 'pgsql',
+            }.get(engine, engine)
             url = make_url(sqlalchemy_engine.url)
             return { 'htsql': { 'db': DB(engine=engine, 
                                          database=url.database,
                      'engine.%s' % engine : {}}
         return {}
 
+

src/htsql_tweak/sqlalchemy/connect.py

             # that is in the SQLAlchemy connection pool.
             return wrapper.connection
         return super(SQLAlchemyConnect, self).open()
+
+

src/htsql_tweak/sqlalchemy/introspect.py

                                UniqueConstraint)
 
 
+def decode(name, quote=None):
+    if not name:
+        name = ""
+    if not quote and name == name.lower():
+        if context.app.htsql.db.engine == 'oracle':
+            name = name.upper()
+    if isinstance(name, str):
+        name = name.decode('utf-8')
+    return name
+
+
 class SQLAlchemyIntrospect(Introspect):
     """ override normal introspection with SQLAlchemy's MetaData """
 
         catalog = make_catalog()
 
         for table_record in metadata.sorted_tables:
-            schema_name = table_record.schema or ''
+            schema_name = decode(table_record.schema,
+                                 table_record.quote_schema)
             if schema_name not in catalog.schemas:
                 catalog.add_schema(schema_name)
             schema = catalog.schemas[schema_name]
-            table = schema.add_table(table_record.name)
+            name = decode(table_record.name, table_record.quote)
+            table = schema.add_table(name)
 
             for column_record in table_record.columns:
+                name = decode(column_record.name, column_record.quote)
                 introspect_domain = IntrospectSADomain(column_record.type)
                 domain = introspect_domain()
                 is_nullable = column_record.nullable
                 has_default = (column_record.server_default is not None)
-                table.add_column(column_record.name, domain,
-                                 is_nullable, has_default)
+                table.add_column(name, domain, is_nullable, has_default)
 
         for table_record in metadata.sorted_tables:
-            schema_name = table_record.schema or ''
+            schema_name = decode(table_record.schema,
+                                 table_record.quote_schema)
             schema = catalog.schemas[schema_name]
-            table = schema.tables[table_record.name]
+            name = decode(table_record.name, table_record.quote)
+            table = schema.tables[name]
 
             for key_record in table_record.constraints:
                 if isinstance(key_record, (PrimaryKeyConstraint,
                                            UniqueConstraint)):
-                    names = [column_record.name
-                             if not isinstance(column_record, str)
-                             else column_record
+                    names = [decode(column_record.name, column_record.quote)
                              for column_record in key_record.columns]
                     if not all(name in table.columns for name in names):
                         continue
                     is_primary = isinstance(key_record, PrimaryKeyConstraint)
                     table.add_unique_key(columns, is_primary)
                 elif isinstance(key_record, ForeignKeyConstraint):
-                    names = [column_record.name
-                             if not isinstance(column_record, str)
-                             else column_record
-                             for column_record in key_record.columns]
+                    column_records = [table_record.columns[column_record]
+                                      if isinstance(column_record, basestring)
+                                      else column_record
+                                      for column_record in key_record.columns]
+                    names = [decode(column_record.name, column_record.quote)
+                             for column_record in column_records]
                     if not all(name in table.columns for name in names):
                         continue
                     columns = [table.columns[name] for name in names]
                     target_records = [element.column
                                       for element in key_record.elements]
                     target_table_record = target_records[0].table
-                    target_schema_name = target_table_record.schema or ''
+                    target_schema_name = decode(target_table_record.schema,
+                                            target_table_record.quote_schema)
                     if target_schema_name not in catalog.schemas:
                         continue
                     target_schema = catalog.schemas[target_schema_name]
-                    if target_table_record.name not in target_schema.tables:
+                    target_table_name = decode(target_table_record.name,
+                                               target_table_record.quote)
+                    if target_table_name not in target_schema.tables:
                         continue
-                    target_table = \
-                            target_schema.tables[target_table_record.name]
-                    target_names = [target_record.name
+                    target_table = target_schema.tables[target_table_name]
+                    target_names = [decode(target_record.name,
+                                           target_record.quote)
                                     for target_record in target_records]
                     if not all(name in target_table.columns
                                for name in target_names):

src/htsql_tweak/timeout/pgsql/__init__.py

 class TweakTimeoutPGSQLAddon(Addon):
 
     name = 'tweak.timeout.pgsql'
-    hint = """implements query timeout for PostgreSQL"""
-    help = """
-      This plugin is used to set the query timeout using PostgreSQL
-      specific connection parameter.
-    """
+    hint = """implement `tweak.timeout` for PostgreSQL"""
     prerequisites = ['engine.pgsql']
 
 

src/htsql_tweak/view/__init__.py

 #
 
 
-from htsql.addon import Addon
+from htsql.addon import Addon, addon_registry
 
 
 class TweakViewAddon(Addon):
 
     name = 'tweak.view'
-    hint = """guesses links for views"""
+    hint = """add constraints for views"""
     help = """
-      This plugin attempts to guess at various links 
-      between views and tables (where foreign keys are
-      not defined).  This is only supported in PostgreSQL.
+    This addon attempts to infer foreign keys and other constraints
+    from a view definition.
+
+    Currently, only PostgreSQL backend is supported.
     """
 
     @classmethod
     def get_extension(cls, app, attributes):
-        return 'tweak.view.%s' % app.htsql.db.engine
+        if app.htsql.db is not None:
+            name = '%s.%s' % (cls.name, app.htsql.db.engine)
+            if name not in addon_registry:
+                raise ImportError("%s is not implemented for %s"
+                                  % (cls.name, app.htsql.db.engine))
+            return name
 
 

src/htsql_tweak/view/pgsql/__init__.py

 
     name = 'tweak.view.pgsql'
     prerequisites = ['engine.pgsql']
+    hint = """implement `tweak.view` for PostgreSQL"""
 
 

test/adhoc/test_sqlalchemy.py

-#
-# Copyright (c) 2006-2011, Prometheus Research, LLC
-# This example source is released under the MIT License
-#
-import os, yaml
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy import (Column, Integer, String, Enum, Text, Table,
-                        ForeignKey, ForeignKeyConstraint, create_engine)
-from sqlalchemy.orm import relationship, backref, sessionmaker
-
-Base = declarative_base()
-
-class School(Base):
-    __table__ = Table('school', Base.metadata,
-        Column('code', String(16), primary_key=True),
-        Column('name', String(64), nullable=False, unique=True),
-        Column('campus', Enum('old','north','south', native_enum=False)),
-    )
-
-    def __init__(self, code, name, campus):
-        self.code = code
-        self.name = name
-        self.campus = campus
-
-class Department(Base):
-    __tablename__ = 'department'
-    code = Column(String(16), primary_key=True)
-    name = Column(String(64), nullable=False, unique=True)
-    school_code = Column(String(16), ForeignKey('school.code'))
-
-    school = relationship('School', 
-               backref=backref('department', lazy='dynamic'))
-
-    def __init__(self, code, name, school_code):
-        self.code = code
-        self.name = name
-        self.school_code = school_code
-
-class Program(Base):
-    __tablename__ = 'program'
-    school_code = Column(String(16), ForeignKey('school.code'), 
-                         primary_key=True)
-    code = Column(String(16), primary_key=True)
-    title = Column(String(64), nullable=False, unique=True)
-    degree = Column(Enum('ba','bs','ct','pb','ma','ms','ph'))
-    part_of = Column(String(16)) 
-
-    school = relationship('School', 
-               backref=backref('program', lazy='dynamic'))
-
-    __table_args__ = (ForeignKeyConstraint(('school_code','part_of'),
-                             ('program.school_code','program.code')), {} )
-
-    def __init__(self, school_code, code, title, degree, part_of):
-        self.school_code = school_code
-        self.code = code
-        self.title = title
-        self.degree = degree
-        self.part_of = part_of
-
-class Course(Base):
-    __tablename__ = 'course'
-    department_code = Column(String(16), ForeignKey('department.code'),
-                              primary_key=True)
-    no = Column(Integer, primary_key=True)
-    title = Column(String(64), nullable=False, unique=True)
-    credits = Column(Integer)
-    description = Column(Text)
-    
-    department = relationship('Department',
-                   backref=backref('course', lazy='dynamic'))
-
-    def __init__(self, department_code, no, title, credits, description):
-        self.department_code = department_code
-        self.no = no
-        self.title = title
-        self.credits = credits
-        self.description = description
-
-
-def populate(engine):
-    Base.metadata.create_all(engine)
-    fn = os.path.join(os.path.dirname(__file__), '..', '..', 
-                      'test', 'regress', 'sql', 'regress-data.yaml')
-    regress = yaml.load(open(fn).read())
-    Session = sessionmaker(bind=engine)
-    session = Session()
-    for (code, name, campus) in regress[0]['data']:
-        session.add(School(code, name, campus))       
-    for (code, name, school_code) in regress[1]['data']:
-        session.add(Department(code, name, school_code))       
-    for (school_code, code, title, degree, part_of) in regress[2]['data']:
-        session.add(Program(school_code, code, title, degree, part_of))
-    for (dept_code, no, title, credits, description) in regress[3]['data']:
-        session.add(Course(dept_code, no, title, credits, description))
-    session.commit()
-    return (session.query(School).count(),
-            session.query(Program).count(),
-            session.query(Department).count(),
-            session.query(Course).count())
-
-def extract(engine, metadata=None):
-    """ extract meta-data from module """
-    return { 'driver': engine.driver,
-    }
-            
-engine = create_engine("sqlite:///:memory:")
-metadata = Base.metadata
-populate(engine)
-metadata.bind = engine
-
-from htsql import HTSQL
-from htsql.request import produce
-htsql = HTSQL(None, {'tweak.sqlalchemy': {'engine': engine, 
-                                          'metadata': metadata }})
-with htsql:
-   for row in produce("/{'Hello World'}"):
-       print row
-   for row in produce("/department{school.campus, name}"):
-       print row
-

test/code/test_sqlalchemy.py

+
+from sqlalchemy import (create_engine, MetaData, Table, Column, ForeignKey,
+                        ForeignKeyConstraint, Integer, String, Text, Enum)
+
+metadata = MetaData()
+
+schema = None
+prefix = ''
+if demo.engine in ['pgsql', 'mssql']:
+    schema = 'ad'
+    prefix = schema+'.'
+
+Table('school', metadata,
+      Column('code', String(16), primary_key=True),
+      Column('name', String(64), nullable=False, unique=True),
+      Column('campus', Enum('old', 'north', 'south')),
+      schema=schema)
+
+Table('department', metadata,
+      Column('code', String(16), primary_key=True),
+      Column('name', String(64), nullable=False, unique=True),
+      Column('school_code', String(16), ForeignKey(prefix+'school.code')),
+      schema=schema)
+
+Table('program', metadata,
+      Column('school_code', String(16), ForeignKey(prefix+'school.code'),
+             primary_key=True),
+      Column('code', String(16), primary_key=True),
+      Column('title', String(64), nullable=False, unique=True),
+      Column('degree', Enum('ba', 'bs', 'ct', 'ma', 'ms', 'ph')),
+      Column('part_of_code', String(16)),
+      ForeignKeyConstraint(['school_code',
+                            'part_of_code'],
+                           [prefix+'program.school_code',
+                            prefix+'program.code']),
+      schema=schema)
+
+Table('course', metadata,
+      Column('department_code', String(16),
+             ForeignKey(prefix+'department.code'), primary_key=True),
+      Column('no', Integer, primary_key=True),
+      Column('title', String(64), nullable=False, unique=True),
+      Column('credits', Integer),
+      Column('description', Text),
+      schema=schema)
+
+uri = None
+if demo.engine == 'sqlite':
+    uri = "sqlite:///%s" % demo.database
+else:
+    uri = ""
+    if demo.host is not None:
+        uri += demo.host
+        if demo.port is not None:
+            uri += ":"+str(demo.port)
+    if demo.username is not None:
+        uri = "@"+uri
+        if demo.password is not None:
+            uri = ":"+demo.password+uri
+        uri = demo.username+uri
+    scheme = ""
+    if demo.engine == 'pgsql':
+        scheme = "postgresql"
+    elif demo.engine == 'mysql':
+        scheme = "mysql"
+    elif demo.engine == 'oracle':
+        scheme = "oracle"
+    elif demo.engine == 'mssql':
+        scheme = "mssql+pymssql"
+    uri = "%s://%s/%s" % (scheme, uri, demo.database)
+
+engine = create_engine(uri)
+metadata.bind = engine
+
+

test/input/addon.yaml

     headers:
       Accept: text/html
 
+# TWEAK.SQLALCHEMY - adapt to SQLAlchemy
+- py: has-sqlalchemy
+  code: |
+    try:
+        import sqlalchemy
+        if sqlalchemy.__version__ > '0.7.':
+            state.toggles.add('sqlalchemy')
+    except ImportError:
+        pass
+- title: tweak.sqlalchemy
+  ifdef: sqlalchemy
+  tests:
+  # Addon description
+  - ctl: [ext, tweak.sqlalchemy]
+
+  # Make sure `test_sqlalchemy` could be found
+  - py: add-module-path
+    code: |
+      import __builtin__, sys, os, os.path
+      path = os.path.join(os.getcwd(), "test/code")
+      sys.path.insert(0, path)
+      __builtin__.demo = state.saves['demo'][0]
+
+  # Initialize HTSQL using SQLAlchemy metadata
+  - db: null
+    extensions:
+      tweak.sqlalchemy:
+        engine: test_sqlalchemy.engine
+        metadata: test_sqlalchemy.metadata
+
+  # Check if columns are recognized
+  - uri: /school{code, name, campus}?code='art'
+  - uri: /program{school_code, code, title, degree, part_of_code}
+                ?school_code='ns'&code='gmth'
+  - uri: /department{code, name, school_code}
+                ?code='mth'
+  - uri: /course{department_code, no, title, credits, description}
+                ?department_code='astro'&no=215
+
+  # Check if links are recognized
+  - uri: /(school?code='art').department{name}
+  - uri: /(school?code='art').program{title}
+  - uri: /(department?code='mth').school{name}
+  - uri: /(department?code='mth').course{title}
+  - uri: /(program?school_code='ns'&code='gmth').school{name}
+  - uri: /(program?school_code='ns'&code='gmth').part_of{title}
+  - uri: /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+  - uri: /(course?department_code='astro'&no=215).department{name}
+
+  # Restore the original `sys.path`
+  - py: remove-module-path
+    code: |
+      import __builtin__, sys, os, os.path
+      path = os.path.join(os.getcwd(), "test/code")
+      sys.path.remove(path)
+      del sys.modules['test_sqlalchemy']
+      del __builtin__.demo
+
 # TWEAK.SYSTEM - add access to system tables
 - title: tweak.system
   ifdef: pgsql

test/output/mssql.yaml

 
             </html>
 
+      - py: has-sqlalchemy
+        stdout: ''
+      - id: tweak.sqlalchemy
+        tests:
+        - ctl: [ext, tweak.sqlalchemy]
+          stdout: |+
+            TWEAK.SQLALCHEMY - use SQLAlchemy engine and model
+
+            This addon provides SQLAlchemy integration in two ways.
+            First, it permits using database connections from SQLAlchemy.
+            Second, it uses SQLAlchemy model instead of introspecting
+            the database.
+
+            Parameter `engine` and `metadata` must point to the SQLAlchemy
+            `engine` and `metadata` objects.  The objects are specified
+            by a dotted string of module names.  The last component in
+            the dotted string is a module attribute.
+
+            Parameters:
+              engine=MODULE.NAME       : the SQLAlchemy `engine` object
+              metadata=MODULE.NAME     : the SQLAlchemy `metadata` object
+
+          exit: 0
+        - py: add-module-path
+          stdout: ''
+        - uri: /school{code, name, campus}?code='art'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                                   |
+             +------------------------------------------+
+             | code | name                     | campus |
+            -+------+--------------------------+--------+-
+             | art  | School of Art and Design | old    |
+                                                  (1 row)
+
+             ----
+             /school{code,name,campus}?code='art'
+             SELECT [school].[code],
+                    [school].[name],
+                    [school].[campus]
+             FROM [ad].[school]
+             WHERE ([school].[code] = 'art')
+             ORDER BY 1 ASC
+        - uri: /program{school_code, code, title, degree, part_of_code} ?school_code='ns'&code='gmth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                                                                 |
+             +-------------------------------------------------------------------------+
+             | school_code | code | title                      | degree | part_of_code |
+            -+-------------+------+----------------------------+--------+--------------+-
+             | ns          | gmth | Masters of Science in      | ms     | pmth         |
+             :             :      : Mathematics                :        :              :
+                                                                                 (1 row)
+
+             ----
+             /program{school_code,code,title,degree,part_of_code}?school_code='ns'&code='gmth'
+             SELECT [program].[school_code],
+                    [program].[code],
+                    [program].[title],
+                    [program].[degree],
+                    [program].[part_of_code]
+             FROM [ad].[program]
+             WHERE ([program].[school_code] = 'ns')
+                   AND ([program].[code] = 'gmth')
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /department{code, name, school_code} ?code='mth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department                       |
+             +----------------------------------+
+             | code | name        | school_code |
+            -+------+-------------+-------------+-
+             | mth  | Mathematics | ns          |
+                                          (1 row)
+
+             ----
+             /department{code,name,school_code}?code='mth'
+             SELECT [department].[code],
+                    [department].[name],
+                    [department].[school_code]
+             FROM [ad].[department]
+             WHERE ([department].[code] = 'mth')
+             ORDER BY 1 ASC
+        - uri: /course{department_code, no, title, credits, description} ?department_code='astro'&no=215
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                                                                               |
+             +--------------------------------------------------------------------------------------+
+             | department_code | no  | title           | credits | description                      |
+            -+-----------------+-----+-----------------+---------+----------------------------------+-
+             | astro           | 215 | Space Mechanics |       4 | Principles of space objects      |
+             :                 :     :                 :         : motion, gravitation, General     :
+             :                 :     :                 :         : Theory of Relativity applied to  :
+             :                 :     :                 :         : Astronomy.                       :
+                                                                                              (1 row)
+
+             ----
+             /course{department_code,no,title,credits,description}?department_code='astro'&no=215
+             SELECT [course].[department_code],
+                    [course].[no],
+                    [course].[title],
+                    [course].[credits],
+                    [course].[description]
+             FROM [ad].[course]
+             WHERE ([course].[department_code] = 'astro')
+                   AND ([course].[no] = 215)
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /(school?code='art').department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department  |
+             +-------------+
+             | name        |
+            -+-------------+-
+             | Art History |
+             | Studio Art  |
+                    (2 rows)
+
+             ----
+             /(school?code='art').department{name}
+             SELECT [department].[name]
+             FROM [ad].[school]
+                  INNER JOIN [ad].[department]
+                             ON ([school].[code] = [department].[school_code])
+             WHERE ([school].[code] = 'art')
+             ORDER BY [school].[code] ASC, [department].[code] ASC
+        - uri: /(school?code='art').program{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                         |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | Post Baccalaureate in Art       |
+             : History                         :
+             | Bachelor of Arts in Art History |
+             | Bachelor of Arts in Studio Art  |
+                                        (3 rows)
+
+             ----
+             /(school?code='art').program{title}
+             SELECT [program].[title]
+             FROM [ad].[school]
+                  INNER JOIN [ad].[program]
+                             ON ([school].[code] = [program].[school_code])
+             WHERE ([school].[code] = 'art')
+             ORDER BY [school].[code] ASC, [program].[school_code] ASC, [program].[code] ASC
+        - uri: /(department?code='mth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(department?code='mth').school{name}
+             SELECT [school].[name]
+             FROM [ad].[department]
+                  INNER JOIN [ad].[school]
+                             ON ([department].[school_code] = [school].[code])
+             WHERE ([department].[code] = 'mth')
+             ORDER BY [department].[code] ASC
+        - uri: /(department?code='mth').course{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                          |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | College Algebra I               |
+             | College Algebra II              |
+             | Calculus I                      |
+             | Calculus II                     |
+             | Calculus III                    |
+             | Linear Algebra                  |
+             | Probability and Statistics      |
+             | Ordinary Differential Equations |
+             | College Geometry                |
+             | Partial Differential Equations  |
+             | Modern Algebra                  |
+                                       (11 rows)
+
+             ----
+             /(department?code='mth').course{title}
+             SELECT [course].[title]
+             FROM [ad].[department]
+                  INNER JOIN [ad].[course]
+                             ON ([department].[code] = [course].[department_code])
+             WHERE ([department].[code] = 'mth')
+             ORDER BY [department].[code] ASC, [course].[department_code] ASC, [course].[no] ASC
+        - uri: /(program?school_code='ns'&code='gmth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').school{name}
+             SELECT [school].[name]
+             FROM [ad].[program]
+                  INNER JOIN [ad].[school]
+                             ON ([program].[school_code] = [school].[code])
+             WHERE ([program].[school_code] = 'ns')
+                   AND ([program].[code] = 'gmth')
+             ORDER BY [program].[school_code] ASC, [program].[code] ASC
+        - uri: /(program?school_code='ns'&code='gmth').part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | part_of                    |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Doctorate of Science in    |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').part_of{title}
+             SELECT [program_2].[title]
+             FROM [ad].[program] AS [program_1]
+                  INNER JOIN [ad].[program] AS [program_2]
+                             ON (([program_1].[school_code] = [program_2].[school_code]) AND ([program_1].[part_of_code] = [program_2].[code]))
+             WHERE ([program_1].[school_code] = 'ns')
+                   AND ([program_1].[code] = 'gmth')
+             ORDER BY [program_1].[school_code] ASC, [program_1].[code] ASC
+        - uri: /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program_via_part_of        |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Bachelor of Science in     |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+             SELECT [program_2].[title]
+             FROM [ad].[program] AS [program_1]
+                  INNER JOIN [ad].[program] AS [program_2]
+                             ON (([program_1].[school_code] = [program_2].[school_code]) AND ([program_1].[code] = [program_2].[part_of_code]))
+             WHERE ([program_1].[school_code] = 'ns')
+                   AND ([program_1].[code] = 'gmth')
+             ORDER BY [program_1].[school_code] ASC, [program_1].[code] ASC, [program_2].[school_code] ASC, [program_2].[code] ASC
+        - uri: /(course?department_code='astro'&no=215).department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department |
+             +------------+
+             | name       |
+            -+------------+-
+             | Astronomy  |
+                    (1 row)
+
+             ----
+             /(course?department_code='astro'&no=215).department{name}
+             SELECT [department].[name]
+             FROM [ad].[course]
+                  INNER JOIN [ad].[department]
+                             ON ([course].[department_code] = [department].[code])
+             WHERE ([course].[department_code] = 'astro')
+                   AND ([course].[no] = 215)
+             ORDER BY [course].[department_code] ASC, [course].[no] ASC
+        - py: remove-module-path
+          stdout: ''

test/output/mysql.yaml

 
             </html>
 
+      - py: has-sqlalchemy
+        stdout: ''
+      - id: tweak.sqlalchemy
+        tests:
+        - ctl: [ext, tweak.sqlalchemy]
+          stdout: |+
+            TWEAK.SQLALCHEMY - use SQLAlchemy engine and model
+
+            This addon provides SQLAlchemy integration in two ways.
+            First, it permits using database connections from SQLAlchemy.
+            Second, it uses SQLAlchemy model instead of introspecting
+            the database.
+
+            Parameter `engine` and `metadata` must point to the SQLAlchemy
+            `engine` and `metadata` objects.  The objects are specified
+            by a dotted string of module names.  The last component in
+            the dotted string is a module attribute.
+
+            Parameters:
+              engine=MODULE.NAME       : the SQLAlchemy `engine` object
+              metadata=MODULE.NAME     : the SQLAlchemy `metadata` object
+
+          exit: 0
+        - py: add-module-path
+          stdout: ''
+        - uri: /school{code, name, campus}?code='art'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                                   |
+             +------------------------------------------+
+             | code | name                     | campus |
+            -+------+--------------------------+--------+-
+             | art  | School of Art and Design | old    |
+                                                  (1 row)
+
+             ----
+             /school{code,name,campus}?code='art'
+             SELECT `school`.`code`,
+                    `school`.`name`,
+                    `school`.`campus`
+             FROM `school`
+             WHERE (`school`.`code` = 'art')
+             ORDER BY 1 ASC
+        - uri: /program{school_code, code, title, degree, part_of_code} ?school_code='ns'&code='gmth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                                                                 |
+             +-------------------------------------------------------------------------+
+             | school_code | code | title                      | degree | part_of_code |
+            -+-------------+------+----------------------------+--------+--------------+-
+             | ns          | gmth | Masters of Science in      | ms     | pmth         |
+             :             :      : Mathematics                :        :              :
+                                                                                 (1 row)
+
+             ----
+             /program{school_code,code,title,degree,part_of_code}?school_code='ns'&code='gmth'
+             SELECT `program`.`school_code`,
+                    `program`.`code`,
+                    `program`.`title`,
+                    `program`.`degree`,
+                    `program`.`part_of_code`
+             FROM `program`
+             WHERE (`program`.`school_code` = 'ns')
+                   AND (`program`.`code` = 'gmth')
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /department{code, name, school_code} ?code='mth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department                       |
+             +----------------------------------+
+             | code | name        | school_code |
+            -+------+-------------+-------------+-
+             | mth  | Mathematics | ns          |
+                                          (1 row)
+
+             ----
+             /department{code,name,school_code}?code='mth'
+             SELECT `department`.`code`,
+                    `department`.`name`,
+                    `department`.`school_code`
+             FROM `department`
+             WHERE (`department`.`code` = 'mth')
+             ORDER BY 1 ASC
+        - uri: /course{department_code, no, title, credits, description} ?department_code='astro'&no=215
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                                                                               |
+             +--------------------------------------------------------------------------------------+
+             | department_code | no  | title           | credits | description                      |
+            -+-----------------+-----+-----------------+---------+----------------------------------+-
+             | astro           | 215 | Space Mechanics |       4 | Principles of space objects      |
+             :                 :     :                 :         : motion, gravitation, General     :
+             :                 :     :                 :         : Theory of Relativity applied to  :
+             :                 :     :                 :         : Astronomy.                       :
+                                                                                              (1 row)
+
+             ----
+             /course{department_code,no,title,credits,description}?department_code='astro'&no=215
+             SELECT `course`.`department_code`,
+                    `course`.`no`,
+                    `course`.`title`,
+                    `course`.`credits`,
+                    `course`.`description`
+             FROM `course`
+             WHERE (`course`.`department_code` = 'astro')
+                   AND (`course`.`no` = 215)
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /(school?code='art').department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department  |
+             +-------------+
+             | name        |
+            -+-------------+-
+             | Art History |
+             | Studio Art  |
+                    (2 rows)
+
+             ----
+             /(school?code='art').department{name}
+             SELECT `department`.`name`
+             FROM `school`
+                  INNER JOIN `department`
+                             ON (`school`.`code` = `department`.`school_code`)
+             WHERE (`school`.`code` = 'art')
+             ORDER BY `school`.`code` ASC, `department`.`code` ASC
+        - uri: /(school?code='art').program{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                         |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | Post Baccalaureate in Art       |
+             : History                         :
+             | Bachelor of Arts in Art History |
+             | Bachelor of Arts in Studio Art  |
+                                        (3 rows)
+
+             ----
+             /(school?code='art').program{title}
+             SELECT `program`.`title`
+             FROM `school`
+                  INNER JOIN `program`
+                             ON (`school`.`code` = `program`.`school_code`)
+             WHERE (`school`.`code` = 'art')
+             ORDER BY `school`.`code` ASC, `program`.`school_code` ASC, `program`.`code` ASC
+        - uri: /(department?code='mth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(department?code='mth').school{name}
+             SELECT `school`.`name`
+             FROM `department`
+                  INNER JOIN `school`
+                             ON (`department`.`school_code` = `school`.`code`)
+             WHERE (`department`.`code` = 'mth')
+             ORDER BY `department`.`code` ASC
+        - uri: /(department?code='mth').course{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                          |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | College Algebra I               |
+             | College Algebra II              |
+             | Calculus I                      |
+             | Calculus II                     |
+             | Calculus III                    |
+             | Linear Algebra                  |
+             | Probability and Statistics      |
+             | Ordinary Differential Equations |
+             | College Geometry                |
+             | Partial Differential Equations  |
+             | Modern Algebra                  |
+                                       (11 rows)
+
+             ----
+             /(department?code='mth').course{title}
+             SELECT `course`.`title`
+             FROM `department`
+                  INNER JOIN `course`
+                             ON (`department`.`code` = `course`.`department_code`)
+             WHERE (`department`.`code` = 'mth')
+             ORDER BY `department`.`code` ASC, `course`.`department_code` ASC, `course`.`no` ASC
+        - uri: /(program?school_code='ns'&code='gmth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').school{name}
+             SELECT `school`.`name`
+             FROM `program`
+                  INNER JOIN `school`
+                             ON (`program`.`school_code` = `school`.`code`)
+             WHERE (`program`.`school_code` = 'ns')
+                   AND (`program`.`code` = 'gmth')
+             ORDER BY `program`.`school_code` ASC, `program`.`code` ASC
+        - uri: /(program?school_code='ns'&code='gmth').part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | part_of                    |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Doctorate of Science in    |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').part_of{title}
+             SELECT `program_2`.`title`
+             FROM `program` AS `program_1`
+                  INNER JOIN `program` AS `program_2`
+                             ON ((`program_1`.`school_code` = `program_2`.`school_code`) AND (`program_1`.`part_of_code` = `program_2`.`code`))
+             WHERE (`program_1`.`school_code` = 'ns')
+                   AND (`program_1`.`code` = 'gmth')
+             ORDER BY `program_1`.`school_code` ASC, `program_1`.`code` ASC
+        - uri: /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program_via_part_of        |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Bachelor of Science in     |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+             SELECT `program_2`.`title`
+             FROM `program` AS `program_1`
+                  INNER JOIN `program` AS `program_2`
+                             ON ((`program_1`.`school_code` = `program_2`.`school_code`) AND (`program_1`.`code` = `program_2`.`part_of_code`))
+             WHERE (`program_1`.`school_code` = 'ns')
+                   AND (`program_1`.`code` = 'gmth')
+             ORDER BY `program_1`.`school_code` ASC, `program_1`.`code` ASC, `program_2`.`school_code` ASC, `program_2`.`code` ASC
+        - uri: /(course?department_code='astro'&no=215).department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department |
+             +------------+
+             | name       |
+            -+------------+-
+             | Astronomy  |
+                    (1 row)
+
+             ----
+             /(course?department_code='astro'&no=215).department{name}
+             SELECT `department`.`name`
+             FROM `course`
+                  INNER JOIN `department`
+                             ON (`course`.`department_code` = `department`.`code`)
+             WHERE (`course`.`department_code` = 'astro')
+                   AND (`course`.`no` = 215)
+             ORDER BY `course`.`department_code` ASC, `course`.`no` ASC
+        - py: remove-module-path
+          stdout: ''

test/output/oracle.yaml

 
             </html>
 
+      - py: has-sqlalchemy
+        stdout: ''
+      - id: tweak.sqlalchemy
+        tests:
+        - ctl: [ext, tweak.sqlalchemy]
+          stdout: |+
+            TWEAK.SQLALCHEMY - use SQLAlchemy engine and model
+
+            This addon provides SQLAlchemy integration in two ways.
+            First, it permits using database connections from SQLAlchemy.
+            Second, it uses SQLAlchemy model instead of introspecting
+            the database.
+
+            Parameter `engine` and `metadata` must point to the SQLAlchemy
+            `engine` and `metadata` objects.  The objects are specified
+            by a dotted string of module names.  The last component in
+            the dotted string is a module attribute.
+
+            Parameters:
+              engine=MODULE.NAME       : the SQLAlchemy `engine` object
+              metadata=MODULE.NAME     : the SQLAlchemy `metadata` object
+
+          exit: 0
+        - py: add-module-path
+          stdout: ''
+        - uri: /school{code, name, campus}?code='art'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                                   |
+             +------------------------------------------+
+             | code | name                     | campus |
+            -+------+--------------------------+--------+-
+             | art  | School of Art and Design | old    |
+                                                  (1 row)
+
+             ----
+             /school{code,name,campus}?code='art'
+             SELECT "SCHOOL"."CODE",
+                    "SCHOOL"."NAME",
+                    "SCHOOL"."CAMPUS"
+             FROM "SCHOOL"
+             WHERE ("SCHOOL"."CODE" = 'art')
+             ORDER BY 1 ASC
+        - uri: /program{school_code, code, title, degree, part_of_code} ?school_code='ns'&code='gmth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                                                                 |
+             +-------------------------------------------------------------------------+
+             | school_code | code | title                      | degree | part_of_code |
+            -+-------------+------+----------------------------+--------+--------------+-
+             | ns          | gmth | Masters of Science in      | ms     | pmth         |
+             :             :      : Mathematics                :        :              :
+                                                                                 (1 row)
+
+             ----
+             /program{school_code,code,title,degree,part_of_code}?school_code='ns'&code='gmth'
+             SELECT "PROGRAM"."SCHOOL_CODE",
+                    "PROGRAM"."CODE",
+                    "PROGRAM"."TITLE",
+                    "PROGRAM"."DEGREE",
+                    "PROGRAM"."PART_OF_CODE"
+             FROM "PROGRAM"
+             WHERE ("PROGRAM"."SCHOOL_CODE" = 'ns')
+                   AND ("PROGRAM"."CODE" = 'gmth')
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /department{code, name, school_code} ?code='mth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department                       |
+             +----------------------------------+
+             | code | name        | school_code |
+            -+------+-------------+-------------+-
+             | mth  | Mathematics | ns          |
+                                          (1 row)
+
+             ----
+             /department{code,name,school_code}?code='mth'
+             SELECT "DEPARTMENT"."CODE",
+                    "DEPARTMENT"."NAME",
+                    "DEPARTMENT"."SCHOOL_CODE"
+             FROM "DEPARTMENT"
+             WHERE ("DEPARTMENT"."CODE" = 'mth')
+             ORDER BY 1 ASC
+        - uri: /course{department_code, no, title, credits, description} ?department_code='astro'&no=215
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                                                                               |
+             +--------------------------------------------------------------------------------------+
+             | department_code | no  | title           | credits | description                      |
+            -+-----------------+-----+-----------------+---------+----------------------------------+-
+             | astro           | 215 | Space Mechanics |       4 | Principles of space objects      |
+             :                 :     :                 :         : motion, gravitation, General     :
+             :                 :     :                 :         : Theory of Relativity applied to  :
+             :                 :     :                 :         : Astronomy.                       :
+                                                                                              (1 row)
+
+             ----
+             /course{department_code,no,title,credits,description}?department_code='astro'&no=215
+             SELECT "COURSE"."DEPARTMENT_CODE",
+                    "COURSE"."NO",
+                    "COURSE"."TITLE",
+                    "COURSE"."CREDITS",
+                    "COURSE"."DESCRIPTION"
+             FROM "COURSE"
+             WHERE ("COURSE"."DEPARTMENT_CODE" = 'astro')
+                   AND ("COURSE"."NO" = 215)
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /(school?code='art').department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department  |
+             +-------------+
+             | name        |
+            -+-------------+-
+             | Art History |
+             | Studio Art  |
+                    (2 rows)
+
+             ----
+             /(school?code='art').department{name}
+             SELECT "DEPARTMENT"."NAME"
+             FROM "SCHOOL"
+                  INNER JOIN "DEPARTMENT"
+                             ON ("SCHOOL"."CODE" = "DEPARTMENT"."SCHOOL_CODE")
+             WHERE ("SCHOOL"."CODE" = 'art')
+             ORDER BY "SCHOOL"."CODE" ASC, "DEPARTMENT"."CODE" ASC
+        - uri: /(school?code='art').program{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                         |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | Post Baccalaureate in Art       |
+             : History                         :
+             | Bachelor of Arts in Art History |
+             | Bachelor of Arts in Studio Art  |
+                                        (3 rows)
+
+             ----
+             /(school?code='art').program{title}
+             SELECT "PROGRAM"."TITLE"
+             FROM "SCHOOL"
+                  INNER JOIN "PROGRAM"
+                             ON ("SCHOOL"."CODE" = "PROGRAM"."SCHOOL_CODE")
+             WHERE ("SCHOOL"."CODE" = 'art')
+             ORDER BY "SCHOOL"."CODE" ASC, "PROGRAM"."SCHOOL_CODE" ASC, "PROGRAM"."CODE" ASC
+        - uri: /(department?code='mth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(department?code='mth').school{name}
+             SELECT "SCHOOL"."NAME"
+             FROM "DEPARTMENT"
+                  INNER JOIN "SCHOOL"
+                             ON ("DEPARTMENT"."SCHOOL_CODE" = "SCHOOL"."CODE")
+             WHERE ("DEPARTMENT"."CODE" = 'mth')
+             ORDER BY "DEPARTMENT"."CODE" ASC
+        - uri: /(department?code='mth').course{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                          |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | College Algebra I               |
+             | College Algebra II              |
+             | Calculus I                      |
+             | Calculus II                     |
+             | Calculus III                    |
+             | Linear Algebra                  |
+             | Probability and Statistics      |
+             | Ordinary Differential Equations |
+             | College Geometry                |
+             | Partial Differential Equations  |
+             | Modern Algebra                  |
+                                       (11 rows)
+
+             ----
+             /(department?code='mth').course{title}
+             SELECT "COURSE"."TITLE"
+             FROM "DEPARTMENT"
+                  INNER JOIN "COURSE"
+                             ON ("DEPARTMENT"."CODE" = "COURSE"."DEPARTMENT_CODE")
+             WHERE ("DEPARTMENT"."CODE" = 'mth')
+             ORDER BY "DEPARTMENT"."CODE" ASC, "COURSE"."DEPARTMENT_CODE" ASC, "COURSE"."NO" ASC
+        - uri: /(program?school_code='ns'&code='gmth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').school{name}
+             SELECT "SCHOOL"."NAME"
+             FROM "PROGRAM"
+                  INNER JOIN "SCHOOL"
+                             ON ("PROGRAM"."SCHOOL_CODE" = "SCHOOL"."CODE")
+             WHERE ("PROGRAM"."SCHOOL_CODE" = 'ns')
+                   AND ("PROGRAM"."CODE" = 'gmth')
+             ORDER BY "PROGRAM"."SCHOOL_CODE" ASC, "PROGRAM"."CODE" ASC
+        - uri: /(program?school_code='ns'&code='gmth').part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | part_of                    |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Doctorate of Science in    |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').part_of{title}
+             SELECT "PROGRAM_2"."TITLE"
+             FROM "PROGRAM" "PROGRAM_1"
+                  INNER JOIN "PROGRAM" "PROGRAM_2"
+                             ON (("PROGRAM_1"."SCHOOL_CODE" = "PROGRAM_2"."SCHOOL_CODE") AND ("PROGRAM_1"."PART_OF_CODE" = "PROGRAM_2"."CODE"))
+             WHERE ("PROGRAM_1"."SCHOOL_CODE" = 'ns')
+                   AND ("PROGRAM_1"."CODE" = 'gmth')
+             ORDER BY "PROGRAM_1"."SCHOOL_CODE" ASC, "PROGRAM_1"."CODE" ASC
+        - uri: /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program_via_part_of        |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Bachelor of Science in     |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+             SELECT "PROGRAM_2"."TITLE"
+             FROM "PROGRAM" "PROGRAM_1"
+                  INNER JOIN "PROGRAM" "PROGRAM_2"
+                             ON (("PROGRAM_1"."SCHOOL_CODE" = "PROGRAM_2"."SCHOOL_CODE") AND ("PROGRAM_1"."CODE" = "PROGRAM_2"."PART_OF_CODE"))
+             WHERE ("PROGRAM_1"."SCHOOL_CODE" = 'ns')
+                   AND ("PROGRAM_1"."CODE" = 'gmth')
+             ORDER BY "PROGRAM_1"."SCHOOL_CODE" ASC, "PROGRAM_1"."CODE" ASC, "PROGRAM_2"."SCHOOL_CODE" ASC, "PROGRAM_2"."CODE" ASC
+        - uri: /(course?department_code='astro'&no=215).department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department |
+             +------------+
+             | name       |
+            -+------------+-
+             | Astronomy  |
+                    (1 row)
+
+             ----
+             /(course?department_code='astro'&no=215).department{name}
+             SELECT "DEPARTMENT"."NAME"
+             FROM "COURSE"
+                  INNER JOIN "DEPARTMENT"
+                             ON ("COURSE"."DEPARTMENT_CODE" = "DEPARTMENT"."CODE")
+             WHERE ("COURSE"."DEPARTMENT_CODE" = 'astro')
+                   AND ("COURSE"."NO" = 215)
+             ORDER BY "COURSE"."DEPARTMENT_CODE" ASC, "COURSE"."NO" ASC
+        - py: remove-module-path
+          stdout: ''

test/output/pgsql.yaml

 
             </html>
 
+      - py: has-sqlalchemy
+        stdout: ''
+      - id: tweak.sqlalchemy
+        tests:
+        - ctl: [ext, tweak.sqlalchemy]
+          stdout: |+
+            TWEAK.SQLALCHEMY - use SQLAlchemy engine and model
+
+            This addon provides SQLAlchemy integration in two ways.
+            First, it permits using database connections from SQLAlchemy.
+            Second, it uses SQLAlchemy model instead of introspecting
+            the database.
+
+            Parameter `engine` and `metadata` must point to the SQLAlchemy
+            `engine` and `metadata` objects.  The objects are specified
+            by a dotted string of module names.  The last component in
+            the dotted string is a module attribute.
+
+            Parameters:
+              engine=MODULE.NAME       : the SQLAlchemy `engine` object
+              metadata=MODULE.NAME     : the SQLAlchemy `metadata` object
+
+          exit: 0
+        - py: add-module-path
+          stdout: ''
+        - uri: /school{code, name, campus}?code='art'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                                   |
+             +------------------------------------------+
+             | code | name                     | campus |
+            -+------+--------------------------+--------+-
+             | art  | School of Art and Design | old    |
+                                                  (1 row)
+
+             ----
+             /school{code,name,campus}?code='art'
+             SELECT "school"."code",
+                    "school"."name",
+                    "school"."campus"
+             FROM "ad"."school"
+             WHERE ("school"."code" = 'art')
+             ORDER BY 1 ASC
+        - uri: /program{school_code, code, title, degree, part_of_code} ?school_code='ns'&code='gmth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                                                                 |
+             +-------------------------------------------------------------------------+
+             | school_code | code | title                      | degree | part_of_code |
+            -+-------------+------+----------------------------+--------+--------------+-
+             | ns          | gmth | Masters of Science in      | ms     | pmth         |
+             :             :      : Mathematics                :        :              :
+                                                                                 (1 row)
+
+             ----
+             /program{school_code,code,title,degree,part_of_code}?school_code='ns'&code='gmth'
+             SELECT "program"."school_code",
+                    "program"."code",
+                    "program"."title",
+                    "program"."degree",
+                    "program"."part_of_code"
+             FROM "ad"."program"
+             WHERE ("program"."school_code" = 'ns')
+                   AND ("program"."code" = 'gmth')
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /department{code, name, school_code} ?code='mth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department                       |
+             +----------------------------------+
+             | code | name        | school_code |
+            -+------+-------------+-------------+-
+             | mth  | Mathematics | ns          |
+                                          (1 row)
+
+             ----
+             /department{code,name,school_code}?code='mth'
+             SELECT "department"."code",
+                    "department"."name",
+                    "department"."school_code"
+             FROM "ad"."department"
+             WHERE ("department"."code" = 'mth')
+             ORDER BY 1 ASC
+        - uri: /course{department_code, no, title, credits, description} ?department_code='astro'&no=215
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                                                                               |
+             +--------------------------------------------------------------------------------------+
+             | department_code | no  | title           | credits | description                      |
+            -+-----------------+-----+-----------------+---------+----------------------------------+-
+             | astro           | 215 | Space Mechanics |       4 | Principles of space objects      |
+             :                 :     :                 :         : motion, gravitation, General     :
+             :                 :     :                 :         : Theory of Relativity applied to  :
+             :                 :     :                 :         : Astronomy.                       :
+                                                                                              (1 row)
+
+             ----
+             /course{department_code,no,title,credits,description}?department_code='astro'&no=215
+             SELECT "course"."department_code",
+                    "course"."no",
+                    "course"."title",
+                    "course"."credits",
+                    "course"."description"
+             FROM "ad"."course"
+             WHERE ("course"."department_code" = 'astro')
+                   AND ("course"."no" = 215)
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /(school?code='art').department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department  |
+             +-------------+
+             | name        |
+            -+-------------+-
+             | Art History |
+             | Studio Art  |
+                    (2 rows)
+
+             ----
+             /(school?code='art').department{name}
+             SELECT "department"."name"
+             FROM "ad"."school"
+                  INNER JOIN "ad"."department"
+                             ON ("school"."code" = "department"."school_code")
+             WHERE ("school"."code" = 'art')
+             ORDER BY "school"."code" ASC, "department"."code" ASC
+        - uri: /(school?code='art').program{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                         |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | Post Baccalaureate in Art       |
+             : History                         :
+             | Bachelor of Arts in Art History |
+             | Bachelor of Arts in Studio Art  |
+                                        (3 rows)
+
+             ----
+             /(school?code='art').program{title}
+             SELECT "program"."title"
+             FROM "ad"."school"
+                  INNER JOIN "ad"."program"
+                             ON ("school"."code" = "program"."school_code")
+             WHERE ("school"."code" = 'art')
+             ORDER BY "school"."code" ASC, "program"."school_code" ASC, "program"."code" ASC
+        - uri: /(department?code='mth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(department?code='mth').school{name}
+             SELECT "school"."name"
+             FROM "ad"."department"
+                  INNER JOIN "ad"."school"
+                             ON ("department"."school_code" = "school"."code")
+             WHERE ("department"."code" = 'mth')
+             ORDER BY "department"."code" ASC
+        - uri: /(department?code='mth').course{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                          |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | College Algebra I               |
+             | College Algebra II              |
+             | Calculus I                      |
+             | Calculus II                     |
+             | Calculus III                    |
+             | Linear Algebra                  |
+             | Probability and Statistics      |
+             | Ordinary Differential Equations |
+             | College Geometry                |
+             | Partial Differential Equations  |
+             | Modern Algebra                  |
+                                       (11 rows)
+
+             ----
+             /(department?code='mth').course{title}
+             SELECT "course"."title"
+             FROM "ad"."department"
+                  INNER JOIN "ad"."course"
+                             ON ("department"."code" = "course"."department_code")
+             WHERE ("department"."code" = 'mth')
+             ORDER BY "department"."code" ASC, "course"."department_code" ASC, "course"."no" ASC
+        - uri: /(program?school_code='ns'&code='gmth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').school{name}
+             SELECT "school"."name"
+             FROM "ad"."program"
+                  INNER JOIN "ad"."school"
+                             ON ("program"."school_code" = "school"."code")
+             WHERE ("program"."school_code" = 'ns')
+                   AND ("program"."code" = 'gmth')
+             ORDER BY "program"."school_code" ASC, "program"."code" ASC
+        - uri: /(program?school_code='ns'&code='gmth').part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | part_of                    |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Doctorate of Science in    |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').part_of{title}
+             SELECT "program_2"."title"
+             FROM "ad"."program" AS "program_1"
+                  INNER JOIN "ad"."program" AS "program_2"
+                             ON (("program_1"."school_code" = "program_2"."school_code") AND ("program_1"."part_of_code" = "program_2"."code"))
+             WHERE ("program_1"."school_code" = 'ns')
+                   AND ("program_1"."code" = 'gmth')
+             ORDER BY "program_1"."school_code" ASC, "program_1"."code" ASC
+        - uri: /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program_via_part_of        |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Bachelor of Science in     |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+             SELECT "program_2"."title"
+             FROM "ad"."program" AS "program_1"
+                  INNER JOIN "ad"."program" AS "program_2"
+                             ON (("program_1"."school_code" = "program_2"."school_code") AND ("program_1"."code" = "program_2"."part_of_code"))
+             WHERE ("program_1"."school_code" = 'ns')
+                   AND ("program_1"."code" = 'gmth')
+             ORDER BY "program_1"."school_code" ASC, "program_1"."code" ASC, "program_2"."school_code" ASC, "program_2"."code" ASC
+        - uri: /(course?department_code='astro'&no=215).department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department |
+             +------------+
+             | name       |
+            -+------------+-
+             | Astronomy  |
+                    (1 row)
+
+             ----
+             /(course?department_code='astro'&no=215).department{name}
+             SELECT "department"."name"
+             FROM "ad"."course"
+                  INNER JOIN "ad"."department"
+                             ON ("course"."department_code" = "department"."code")
+             WHERE ("course"."department_code" = 'astro')
+                   AND ("course"."no" = 215)
+             ORDER BY "course"."department_code" ASC, "course"."no" ASC
+        - py: remove-module-path
+          stdout: ''
       - id: tweak.system
         tests:
         - ctl: [ext, tweak.system]

test/output/sqlite.yaml

 
             </html>
 
+      - py: has-sqlalchemy
+        stdout: ''
+      - id: tweak.sqlalchemy
+        tests:
+        - ctl: [ext, tweak.sqlalchemy]
+          stdout: |+
+            TWEAK.SQLALCHEMY - use SQLAlchemy engine and model
+
+            This addon provides SQLAlchemy integration in two ways.
+            First, it permits using database connections from SQLAlchemy.
+            Second, it uses SQLAlchemy model instead of introspecting
+            the database.
+
+            Parameter `engine` and `metadata` must point to the SQLAlchemy
+            `engine` and `metadata` objects.  The objects are specified
+            by a dotted string of module names.  The last component in
+            the dotted string is a module attribute.
+
+            Parameters:
+              engine=MODULE.NAME       : the SQLAlchemy `engine` object
+              metadata=MODULE.NAME     : the SQLAlchemy `metadata` object
+
+          exit: 0
+        - py: add-module-path
+          stdout: ''
+        - uri: /school{code, name, campus}?code='art'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                                   |
+             +------------------------------------------+
+             | code | name                     | campus |
+            -+------+--------------------------+--------+-
+             | art  | School of Art and Design | old    |
+                                                  (1 row)
+
+             ----
+             /school{code,name,campus}?code='art'
+             SELECT "school"."code",
+                    "school"."name",
+                    "school"."campus"
+             FROM "school"
+             WHERE ("school"."code" = 'art')
+             ORDER BY 1 ASC
+        - uri: /program{school_code, code, title, degree, part_of_code} ?school_code='ns'&code='gmth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                                                                 |
+             +-------------------------------------------------------------------------+
+             | school_code | code | title                      | degree | part_of_code |
+            -+-------------+------+----------------------------+--------+--------------+-
+             | ns          | gmth | Masters of Science in      | ms     | pmth         |
+             :             :      : Mathematics                :        :              :
+                                                                                 (1 row)
+
+             ----
+             /program{school_code,code,title,degree,part_of_code}?school_code='ns'&code='gmth'
+             SELECT "program"."school_code",
+                    "program"."code",
+                    "program"."title",
+                    "program"."degree",
+                    "program"."part_of_code"
+             FROM "program"
+             WHERE ("program"."school_code" = 'ns')
+                   AND ("program"."code" = 'gmth')
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /department{code, name, school_code} ?code='mth'
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department                       |
+             +----------------------------------+
+             | code | name        | school_code |
+            -+------+-------------+-------------+-
+             | mth  | Mathematics | ns          |
+                                          (1 row)
+
+             ----
+             /department{code,name,school_code}?code='mth'
+             SELECT "department"."code",
+                    "department"."name",
+                    "department"."school_code"
+             FROM "department"
+             WHERE ("department"."code" = 'mth')
+             ORDER BY 1 ASC
+        - uri: /course{department_code, no, title, credits, description} ?department_code='astro'&no=215
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                                                                               |
+             +--------------------------------------------------------------------------------------+
+             | department_code | no  | title           | credits | description                      |
+            -+-----------------+-----+-----------------+---------+----------------------------------+-
+             | astro           | 215 | Space Mechanics |       4 | Principles of space objects      |
+             :                 :     :                 :         : motion, gravitation, General     :
+             :                 :     :                 :         : Theory of Relativity applied to  :
+             :                 :     :                 :         : Astronomy.                       :
+                                                                                              (1 row)
+
+             ----
+             /course{department_code,no,title,credits,description}?department_code='astro'&no=215
+             SELECT "course"."department_code",
+                    "course"."no",
+                    "course"."title",
+                    "course"."credits",
+                    "course"."description"
+             FROM "course"
+             WHERE ("course"."department_code" = 'astro')
+                   AND ("course"."no" = 215)
+             ORDER BY 1 ASC, 2 ASC
+        - uri: /(school?code='art').department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department  |
+             +-------------+
+             | name        |
+            -+-------------+-
+             | Art History |
+             | Studio Art  |
+                    (2 rows)
+
+             ----
+             /(school?code='art').department{name}
+             SELECT "department"."name"
+             FROM "school"
+                  INNER JOIN "department"
+                             ON ("school"."code" = "department"."school_code")
+             WHERE ("school"."code" = 'art')
+             ORDER BY "school"."code" ASC, "department"."code" ASC
+        - uri: /(school?code='art').program{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program                         |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | Post Baccalaureate in Art       |
+             : History                         :
+             | Bachelor of Arts in Art History |
+             | Bachelor of Arts in Studio Art  |
+                                        (3 rows)
+
+             ----
+             /(school?code='art').program{title}
+             SELECT "program"."title"
+             FROM "school"
+                  INNER JOIN "program"
+                             ON ("school"."code" = "program"."school_code")
+             WHERE ("school"."code" = 'art')
+             ORDER BY "school"."code" ASC, "program"."school_code" ASC, "program"."code" ASC
+        - uri: /(department?code='mth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(department?code='mth').school{name}
+             SELECT "school"."name"
+             FROM "department"
+                  INNER JOIN "school"
+                             ON ("department"."school_code" = "school"."code")
+             WHERE ("department"."code" = 'mth')
+             ORDER BY "department"."code" ASC
+        - uri: /(department?code='mth').course{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | course                          |
+             +---------------------------------+
+             | title                           |
+            -+---------------------------------+-
+             | College Algebra I               |
+             | College Algebra II              |
+             | Calculus I                      |
+             | Calculus II                     |
+             | Calculus III                    |
+             | Linear Algebra                  |
+             | Probability and Statistics      |
+             | Ordinary Differential Equations |
+             | College Geometry                |
+             | Partial Differential Equations  |
+             | Modern Algebra                  |
+                                       (11 rows)
+
+             ----
+             /(department?code='mth').course{title}
+             SELECT "course"."title"
+             FROM "department"
+                  INNER JOIN "course"
+                             ON ("department"."code" = "course"."department_code")
+             WHERE ("department"."code" = 'mth')
+             ORDER BY "department"."code" ASC, "course"."department_code" ASC, "course"."no" ASC
+        - uri: /(program?school_code='ns'&code='gmth').school{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | school                     |
+             +----------------------------+
+             | name                       |
+            -+----------------------------+-
+             | School of Natural Sciences |
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').school{name}
+             SELECT "school"."name"
+             FROM "program"
+                  INNER JOIN "school"
+                             ON ("program"."school_code" = "school"."code")
+             WHERE ("program"."school_code" = 'ns')
+                   AND ("program"."code" = 'gmth')
+             ORDER BY "program"."school_code" ASC, "program"."code" ASC
+        - uri: /(program?school_code='ns'&code='gmth').part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | part_of                    |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Doctorate of Science in    |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').part_of{title}
+             SELECT "program_2"."title"
+             FROM "program" AS "program_1"
+                  INNER JOIN "program" AS "program_2"
+                             ON (("program_1"."school_code" = "program_2"."school_code") AND ("program_1"."part_of_code" = "program_2"."code"))
+             WHERE ("program_1"."school_code" = 'ns')
+                   AND ("program_1"."code" = 'gmth')
+             ORDER BY "program_1"."school_code" ASC, "program_1"."code" ASC
+        - uri: /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | program_via_part_of        |
+             +----------------------------+
+             | title                      |
+            -+----------------------------+-
+             | Bachelor of Science in     |
+             : Mathematics                :
+                                    (1 row)
+
+             ----
+             /(program?school_code='ns'&code='gmth').program_via_part_of{title}
+             SELECT "program_2"."title"
+             FROM "program" AS "program_1"
+                  INNER JOIN "program" AS "program_2"
+                             ON (("program_1"."school_code" = "program_2"."school_code") AND ("program_1"."code" = "program_2"."part_of_code"))
+             WHERE ("program_1"."school_code" = 'ns')
+                   AND ("program_1"."code" = 'gmth')
+             ORDER BY "program_1"."school_code" ASC, "program_1"."code" ASC, "program_2"."school_code" ASC, "program_2"."code" ASC
+        - uri: /(course?department_code='astro'&no=215).department{name}
+          status: 200 OK
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |2
+             | department |
+             +------------+
+             | name       |
+            -+------------+-
+             | Astronomy  |
+                    (1 row)
+
+             ----
+             /(course?department_code='astro'&no=215).department{name}
+             SELECT "department"."name"
+             FROM "course"
+                  INNER JOIN "department"
+                             ON ("course"."department_code" = "department"."code")
+             WHERE ("course"."department_code" = 'astro')
+                   AND ("course"."no" = 215)
+             ORDER BY "course"."department_code" ASC, "course"."no" ASC