]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- A rework to the way that "quoted" identifiers are handled, in that
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 28 Aug 2013 00:43:22 +0000 (20:43 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 28 Aug 2013 00:43:22 +0000 (20:43 -0400)
instead of relying upon various ``quote=True`` flags being passed around,
these flags are converted into rich string objects with quoting information
included at the point at which they are passed to common schema constructs
like :class:`.Table`, :class:`.Column`, etc.   This solves the issue
of various methods that don't correctly honor the "quote" flag such
as :meth:`.Engine.has_table` and related methods.  The :class:`.quoted_name`
object is a string subclass that can also be used explicitly if needed;
the object will hold onto the quoting preferences passed and will
also bypass the "name normalization" performed by dialects that
standardize on uppercase symbols, such as Oracle, Firebird and DB2.
The upshot is that the "uppercase" backends can now work with force-quoted
names, such as lowercase-quoted names and new reserved words.
[ticket:2812]

23 files changed:
doc/build/changelog/changelog_09.rst
doc/build/changelog/migration_09.rst
doc/build/core/metadata.rst
doc/build/core/reflection.rst
doc/build/core/sqlelement.rst
lib/sqlalchemy/dialects/mssql/base.py
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/dialects/oracle/cx_oracle.py
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/engine/base.py
lib/sqlalchemy/engine/default.py
lib/sqlalchemy/engine/reflection.py
lib/sqlalchemy/sql/base.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/schema.py
lib/sqlalchemy/sql/selectable.py
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/sql/util.py
test/ext/declarative/test_basic.py
test/profiles.txt
test/sql/test_metadata.py
test/sql/test_quote.py

index be7a7fc7ced7fa9d8b2c05556ca296f0dd76ddd3..b0492e8970bd1ade56715eb3221f3e2e6db80ecd 100644 (file)
@@ -6,6 +6,28 @@
 .. changelog::
     :version: 0.9.0
 
+    .. change::
+        :tags: bug, sql
+        :tickets: 2812
+
+        A rework to the way that "quoted" identifiers are handled, in that
+        instead of relying upon various ``quote=True`` flags being passed around,
+        these flags are converted into rich string objects with quoting information
+        included at the point at which they are passed to common schema constructs
+        like :class:`.Table`, :class:`.Column`, etc.   This solves the issue
+        of various methods that don't correctly honor the "quote" flag such
+        as :meth:`.Engine.has_table` and related methods.  The :class:`.quoted_name`
+        object is a string subclass that can also be used explicitly if needed;
+        the object will hold onto the quoting preferences passed and will
+        also bypass the "name normalization" performed by dialects that
+        standardize on uppercase symbols, such as Oracle, Firebird and DB2.
+        The upshot is that the "uppercase" backends can now work with force-quoted
+        names, such as lowercase-quoted names and new reserved words.
+
+        .. seealso::
+
+            :ref:`change_2812`
+
     .. change::
         :tags: feature, orm
         :tickets: 2793
index b8328ce7b7e71b919a178a6a6b15db72341dd886..82314cce4ea079997584cc0676212ea60c87620e 100644 (file)
@@ -292,6 +292,34 @@ against ``b_value`` directly.
 
 :ticket:`2751`
 
+.. _change_2812:
+
+Schema identifiers now carry along their own quoting information
+---------------------------------------------------------------------
+
+This change simplifies the Core's usage of so-called "quote" flags, such
+as the ``quote`` flag passed to :class:`.Table` and :class:`.Column`.  The flag
+is now internalized within the string name itself, which is now represented
+as an instance of  :class:`.quoted_name`, a string subclass.   The
+:class:`.IdentifierPreparer` now relies solely on the quoting preferences
+reported by the :class:`.quoted_name` object rather than checking for any
+explicit ``quote`` flags in most cases.   The issue resolved here includes
+that various case-sensitive methods such as :meth:`.Engine.has_table` as well
+as similar methods within dialects now function with explicitly quoted names,
+without the need to complicate or introduce backwards-incompatible changes
+to those APIs (many of which are 3rd party) with the details of quoting flags -
+in particular, a wider range of identifiers now function correctly with the
+so-called "uppercase" backends like Oracle, Firebird, and DB2 (backends that
+store and report upon table and column names using all uppercase for case
+insensitive names).
+
+The :class:`.quoted_name` object is used internally as needed; however if
+other keywords require fixed quoting preferences, the class is available
+publically.
+
+:ticket:`2812`
+
+
 New Features
 ============
 
index e4b50f63a1bb5b67c4c0bad15df1ff17f8c176af..d6fc8c6afd860f7ec04ee91988eabc06e7758b53 100644 (file)
@@ -1,12 +1,15 @@
 .. _metadata_toplevel:
+
 .. _metadata_describing_toplevel:
+
 .. _metadata_describing:
-.. module:: sqlalchemy.schema
 
 ==================================
 Describing Databases with MetaData
 ==================================
 
+.. module:: sqlalchemy.schema
+
 This section discusses the fundamental :class:`.Table`, :class:`.Column`
 and :class:`.MetaData` objects.
 
index 17ff0b99eff83cfdee76306ee1a195c72032df7a..2e9a2de644baf270b113b09b04051f176f8177e1 100644 (file)
@@ -112,6 +112,8 @@ object's dictionary of tables::
     for table in reversed(meta.sorted_tables):
         someengine.execute(table.delete())
 
+.. _metadata_reflection_inspector:
+
 Fine Grained Reflection with Inspector
 --------------------------------------
 
@@ -128,5 +130,5 @@ database is also available. This is known as the "Inspector"::
 .. autoclass:: sqlalchemy.engine.reflection.Inspector
     :members:
     :undoc-members:
-     
+
 
index 953f48c98023232703ef145367789b0ba2571724..0676f18d009b98e78bbd55dcf87d70b1fe387818 100644 (file)
@@ -105,6 +105,8 @@ used to construct any kind of typed SQL expression.
    :members:
    :special-members:
 
+.. autoclass:: sqlalchemy.sql.elements.quoted_name
+
 .. autoclass:: UnaryExpression
    :members:
 
index 7621f4aabe965661e8b4cb992ec1406d8e4bfe78..b1b168035ddd9e6a56f049fe4ad2af87a0538cba 100644 (file)
@@ -1012,7 +1012,7 @@ class MSDDLCompiler(compiler.DDLCompiler):
                           for col in index.kwargs["mssql_include"]]
 
             text += " INCLUDE (%s)" \
-                % ', '.join([preparer.quote(c.name, c.quote)
+                % ', '.join([preparer.quote(c.name)
                              for c in inclusions])
 
         return text
@@ -1035,7 +1035,7 @@ class MSIdentifierPreparer(compiler.IdentifierPreparer):
     def _escape_identifier(self, value):
         return value
 
-    def quote_schema(self, schema, force=True):
+    def quote_schema(self, schema, force=None):
         """Prepare a quoted table and schema name."""
         result = '.'.join([self.quote(x, force) for x in schema.split('.')])
         return result
index b36b707744b5739cb5e16619157879b480018de0..fd6a47a10cb5b99aedab4adb2a519520cb1033c1 100644 (file)
@@ -1451,7 +1451,7 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
                 constraint_string += ", \n\t"
             constraint_string += "KEY %s (%s)" % (
                         self.preparer.quote(
-                            "idx_autoinc_%s" % auto_inc_column.name, None
+                            "idx_autoinc_%s" % auto_inc_column.name
                         ),
                         self.preparer.format_column(auto_inc_column)
                     )
@@ -1557,7 +1557,7 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
 
         if 'mysql_using' in index.kwargs:
             using = index.kwargs['mysql_using']
-            text += " USING %s" % (preparer.quote(using, index.quote))
+            text += " USING %s" % (preparer.quote(using))
 
         return text
 
@@ -1566,8 +1566,7 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
             visit_primary_key_constraint(constraint)
         if "mysql_using" in constraint.kwargs:
             using = constraint.kwargs['mysql_using']
-            text += " USING %s" % (
-                self.preparer.quote(using, constraint.quote))
+            text += " USING %s" % (self.preparer.quote(using))
         return text
 
     def visit_drop_index(self, drop):
index e013799dbd57d28b9b06f86f64ac0b5c16bdb946..b82d3016f6d05143447cb67c99e19d2d1eddbae7 100644 (file)
@@ -362,7 +362,8 @@ class _OracleRowid(oracle.ROWID):
 
 
 class OracleCompiler_cx_oracle(OracleCompiler):
-    def bindparam_string(self, name, quote=None, **kw):
+    def bindparam_string(self, name, **kw):
+        quote = getattr(name, 'quote', None)
         if quote is True or quote is not False and \
             self.preparer._bindparam_requires_quotes(name):
             quoted_name = '"%s"' % name
index 6ccf7190e0d9c3b3afa3c400877fc9f5cd0619ff..8938b3193c1ad13de0db37f59b7e74e788e1272c 100644 (file)
@@ -1094,7 +1094,7 @@ class PGDDLCompiler(compiler.DDLCompiler):
 
         if 'postgresql_using' in index.kwargs:
             using = index.kwargs['postgresql_using']
-            text += "USING %s " % preparer.quote(using, index.quote)
+            text += "USING %s " % preparer.quote(using)
 
         ops = index.kwargs.get('postgresql_ops', {})
         text += "(%s)" \
@@ -1128,7 +1128,7 @@ class PGDDLCompiler(compiler.DDLCompiler):
         elements = []
         for c in constraint.columns:
             op = constraint.operators[c.name]
-            elements.append(self.preparer.quote(c.name, c.quote)+' WITH '+op)
+            elements.append(self.preparer.quote(c.name) + ' WITH '+op)
         text += "EXCLUDE USING %s (%s)" % (constraint.using, ', '.join(elements))
         if constraint.where is not None:
             sqltext = sql_util.expression_as_ddl(constraint.where)
@@ -1250,9 +1250,9 @@ class PGIdentifierPreparer(compiler.IdentifierPreparer):
         if not type_.name:
             raise exc.CompileError("Postgresql ENUM type requires a name.")
 
-        name = self.quote(type_.name, type_.quote)
+        name = self.quote(type_.name)
         if not self.omit_schema and use_schema and type_.schema is not None:
-            name = self.quote_schema(type_.schema, type_.quote) + "." + name
+            name = self.quote_schema(type_.schema) + "." + name
         return name
 
 
index 735113a267f88455b1573ada0566c393448a73c8..9a10e829e2a1e91010699222d90774e58264b54b 100644 (file)
@@ -1672,6 +1672,17 @@ class Engine(Connectable, log.Identified):
             return self.dialect.get_table_names(conn, schema)
 
     def has_table(self, table_name, schema=None):
+        """Return True if the given backend has a table of the given name.
+
+        .. seealso::
+
+            :ref:`metadata_reflection_inspector` - detailed schema inspection using
+            the :class:`.Inspector` interface.
+
+            :class:`.quoted_name` - used to pass quoting information along
+            with a schema identifier.
+
+        """
         return self.run_callable(self.dialect.has_table, table_name, schema)
 
     def raw_connection(self):
index 90c7f5993f721274d236fd3c67489054b6c85491..609375c39913ee41a028c4a0b025547133fec8d1 100644 (file)
@@ -27,6 +27,7 @@ AUTOCOMMIT_REGEXP = re.compile(
             re.I | re.UNICODE)
 
 
+
 class DefaultDialect(interfaces.Dialect):
     """Default implementation of Dialect"""
 
@@ -160,6 +161,7 @@ class DefaultDialect(interfaces.Dialect):
         self._encoder = codecs.getencoder(self.encoding)
         self._decoder = processors.to_unicode_processor_factory(self.encoding)
 
+
     @util.memoized_property
     def _type_memos(self):
         return weakref.WeakKeyDictionary()
index 29ede957929fd0208594a20ed8097f3b11bc18de..340af1abb76d8e03b1ce4be614dea0e5f5c87fbc 100644 (file)
@@ -169,7 +169,7 @@ class Inspector(object):
          database's default schema is
          used, else the named schema is searched.  If the database does not
          support named schemas, behavior is undefined if ``schema`` is not
-         passed as ``None``.
+         passed as ``None``.  For special quoting, use :class:`.quoted_name`.
 
         :param order_by: Optional, may be the string "foreign_key" to sort
          the result on foreign key dependencies.
@@ -206,6 +206,13 @@ class Inspector(object):
 
         This currently includes some options that apply to MySQL tables.
 
+        :param table_name: string name of the table.  For special quoting,
+         use :class:`.quoted_name`.
+
+        :param schema: string schema name; if omitted, uses the default schema
+         of the database connection.  For special quoting,
+         use :class:`.quoted_name`.
+
         """
         if hasattr(self.dialect, 'get_table_options'):
             return self.dialect.get_table_options(
@@ -217,6 +224,8 @@ class Inspector(object):
         """Return all view names in `schema`.
 
         :param schema: Optional, retrieve names from a non-default schema.
+         For special quoting, use :class:`.quoted_name`.
+
         """
 
         return self.dialect.get_view_names(self.bind, schema,
@@ -226,6 +235,8 @@ class Inspector(object):
         """Return definition for `view_name`.
 
         :param schema: Optional, retrieve names from a non-default schema.
+         For special quoting, use :class:`.quoted_name`.
+
         """
 
         return self.dialect.get_view_definition(
@@ -251,6 +262,14 @@ class Inspector(object):
 
         attrs
           dict containing optional column attributes
+
+        :param table_name: string name of the table.  For special quoting,
+         use :class:`.quoted_name`.
+
+        :param schema: string schema name; if omitted, uses the default schema
+         of the database connection.  For special quoting,
+         use :class:`.quoted_name`.
+
         """
 
         col_defs = self.dialect.get_columns(self.bind, table_name, schema,
@@ -288,6 +307,13 @@ class Inspector(object):
         name
           optional name of the primary key constraint.
 
+        :param table_name: string name of the table.  For special quoting,
+         use :class:`.quoted_name`.
+
+        :param schema: string schema name; if omitted, uses the default schema
+         of the database connection.  For special quoting,
+         use :class:`.quoted_name`.
+
         """
         return self.dialect.get_pk_constraint(self.bind, table_name, schema,
                                               info_cache=self.info_cache,
@@ -315,6 +341,13 @@ class Inspector(object):
         name
           optional name of the foreign key constraint.
 
+        :param table_name: string name of the table.  For special quoting,
+         use :class:`.quoted_name`.
+
+        :param schema: string schema name; if omitted, uses the default schema
+         of the database connection.  For special quoting,
+         use :class:`.quoted_name`.
+
         """
 
         return self.dialect.get_foreign_keys(self.bind, table_name, schema,
@@ -336,6 +369,13 @@ class Inspector(object):
         unique
           boolean
 
+        :param table_name: string name of the table.  For special quoting,
+         use :class:`.quoted_name`.
+
+        :param schema: string schema name; if omitted, uses the default schema
+         of the database connection.  For special quoting,
+         use :class:`.quoted_name`.
+
         """
 
         return self.dialect.get_indexes(self.bind, table_name,
@@ -354,6 +394,13 @@ class Inspector(object):
         column_names
           list of column names in order
 
+        :param table_name: string name of the table.  For special quoting,
+         use :class:`.quoted_name`.
+
+        :param schema: string schema name; if omitted, uses the default schema
+         of the database connection.  For special quoting,
+         use :class:`.quoted_name`.
+
         .. versionadded:: 0.9.0
 
         """
index 2a3176d04b833c51c1998269fe093b12e89d9322..85c6e730ea65e84828220da550f2d60d2c57d342 100644 (file)
@@ -30,6 +30,7 @@ class Immutable(object):
         return self
 
 
+
 def _from_objects(*elements):
     return itertools.chain(*[element._from_objects for element in elements])
 
index 5d05cbc2962d32fc8d21d951d5d66d935675cccc..8e3cf17292f1647ab5e7dc06bff3d0ce41f3f49e 100644 (file)
@@ -24,7 +24,7 @@ To generate user-defined SQL strings, see
 
 import re
 from . import schema, sqltypes, operators, functions, \
-        util as sql_util, visitors, elements, selectable
+        util as sql_util, visitors, elements, selectable, base
 from .. import util, exc
 import decimal
 import itertools
@@ -280,10 +280,6 @@ class _CompileLabel(visitors.Visitable):
     def type(self):
         return self.element.type
 
-    @property
-    def quote(self):
-        return self.element.quote
-
 
 class SQLCompiler(Compiled):
     """Default implementation of Compiled.
@@ -548,16 +544,14 @@ class SQLCompiler(Compiled):
         if is_literal:
             name = self.escape_literal_column(name)
         else:
-            name = self.preparer.quote(name, column.quote)
+            name = self.preparer.quote(name)
 
         table = column.table
         if table is None or not include_table or not table.named_with_column:
             return name
         else:
             if table.schema:
-                schema_prefix = self.preparer.quote_schema(
-                                    table.schema,
-                                    table.quote_schema) + '.'
+                schema_prefix = self.preparer.quote_schema(table.schema) + '.'
             else:
                 schema_prefix = ''
             tablename = table.name
@@ -565,7 +559,7 @@ class SQLCompiler(Compiled):
                 tablename = self._truncated_identifier("alias", tablename)
 
             return schema_prefix + \
-                    self.preparer.quote(tablename, table.quote) + \
+                    self.preparer.quote(tablename) + \
                     "." + name
 
     def escape_literal_column(self, text):
@@ -953,7 +947,7 @@ class SQLCompiler(Compiled):
 
         self.binds[bindparam.key] = self.binds[name] = bindparam
 
-        return self.bindparam_string(name, quote=bindparam.quote, **kwargs)
+        return self.bindparam_string(name, **kwargs)
 
     def render_literal_bindparam(self, bindparam, **kw):
         value = bindparam.value
@@ -1023,8 +1017,7 @@ class SQLCompiler(Compiled):
         self.anon_map[derived] = anonymous_counter + 1
         return derived + "_" + str(anonymous_counter)
 
-    def bindparam_string(self, name, quote=None,
-                        positional_names=None, **kw):
+    def bindparam_string(self, name, positional_names=None, **kw):
         if self.positional:
             if positional_names is not None:
                 positional_names.append(name)
@@ -1574,12 +1567,10 @@ class SQLCompiler(Compiled):
                         fromhints=None, **kwargs):
         if asfrom or ashint:
             if getattr(table, "schema", None):
-                ret = self.preparer.quote_schema(table.schema,
-                                table.quote_schema) + \
-                                "." + self.preparer.quote(table.name,
-                                                table.quote)
+                ret = self.preparer.quote_schema(table.schema) + \
+                                "." + self.preparer.quote(table.name)
             else:
-                ret = self.preparer.quote(table.name, table.quote)
+                ret = self.preparer.quote(table.name)
             if fromhints and table in fromhints:
                 ret = self.format_from_hint_text(ret, table,
                                     fromhints[table], iscrud)
@@ -1796,8 +1787,7 @@ class SQLCompiler(Compiled):
         if name is None:
             name = col.key
         bindparam = elements.BindParameter(name, value,
-                            type_=col.type, required=required,
-                            quote=col.quote)
+                            type_=col.type, required=required)
         bindparam._is_crud = True
         return bindparam._compiler_dispatch(self)
 
@@ -2193,11 +2183,11 @@ class DDLCompiler(Compiled):
         return self.sql_compiler.post_process_text(ddl.statement % context)
 
     def visit_create_schema(self, create):
-        schema = self.preparer.format_schema(create.element, create.quote)
+        schema = self.preparer.format_schema(create.element)
         return "CREATE SCHEMA " + schema
 
     def visit_drop_schema(self, drop):
-        schema = self.preparer.format_schema(drop.element, drop.quote)
+        schema = self.preparer.format_schema(drop.element)
         text = "DROP SCHEMA " + schema
         if drop.cascade:
             text += " CASCADE"
@@ -2325,8 +2315,7 @@ class DDLCompiler(Compiled):
     def _prepared_index_name(self, index, include_schema=False):
         if include_schema and index.table is not None and index.table.schema:
             schema = index.table.schema
-            schema_name = self.preparer.quote_schema(schema,
-                                index.table.quote_schema)
+            schema_name = self.preparer.quote_schema(schema)
         else:
             schema_name = None
 
@@ -2340,9 +2329,7 @@ class DDLCompiler(Compiled):
         else:
             self.dialect.validate_identifier(ident)
 
-        index_name = self.preparer.quote(
-                                    ident,
-                                    index.quote)
+        index_name = self.preparer.quote(ident)
 
         if schema_name:
             index_name = schema_name + "." + index_name
@@ -2424,7 +2411,7 @@ class DDLCompiler(Compiled):
             text += "CONSTRAINT %s " % \
                     self.preparer.format_constraint(constraint)
         text += "PRIMARY KEY "
-        text += "(%s)" % ', '.join(self.preparer.quote(c.name, c.quote)
+        text += "(%s)" % ', '.join(self.preparer.quote(c.name)
                                        for c in constraint)
         text += self.define_constraint_deferrability(constraint)
         return text
@@ -2437,11 +2424,11 @@ class DDLCompiler(Compiled):
                         preparer.format_constraint(constraint)
         remote_table = list(constraint._elements.values())[0].column.table
         text += "FOREIGN KEY(%s) REFERENCES %s (%s)" % (
-            ', '.join(preparer.quote(f.parent.name, f.parent.quote)
+            ', '.join(preparer.quote(f.parent.name)
                       for f in constraint._elements.values()),
             self.define_constraint_remote_table(
                             constraint, remote_table, preparer),
-            ', '.join(preparer.quote(f.column.name, f.column.quote)
+            ', '.join(preparer.quote(f.column.name)
                       for f in constraint._elements.values())
         )
         text += self.define_constraint_match(constraint)
@@ -2460,7 +2447,7 @@ class DDLCompiler(Compiled):
             text += "CONSTRAINT %s " % \
                     self.preparer.format_constraint(constraint)
         text += "UNIQUE (%s)" % (
-                    ', '.join(self.preparer.quote(c.name, c.quote)
+                    ', '.join(self.preparer.quote(c.name)
                             for c in constraint))
         text += self.define_constraint_deferrability(constraint)
         return text
@@ -2714,15 +2701,25 @@ class IdentifierPreparer(object):
                 or not self.legal_characters.match(util.text_type(value))
                 or (lc_value != value))
 
-    def quote_schema(self, schema, force):
-        """Quote a schema.
+    def quote_schema(self, schema, force=None):
+        """Conditionally quote a schema.
+
+        Subclasses can override this to provide database-dependent
+        quoting behavior for schema names.
+
+        the 'force' flag should be considered deprecated.
 
-        Subclasses should override this to provide database-dependent
-        quoting behavior.
         """
         return self.quote(schema, force)
 
-    def quote(self, ident, force):
+    def quote(self, ident, force=None):
+        """Conditionally quote an identifier.
+
+        the 'force' flag should be considered deprecated.
+        """
+
+        force = getattr(ident, "quote", None)
+
         if force is None:
             if ident in self._strings:
                 return self._strings[ident]
@@ -2738,38 +2735,35 @@ class IdentifierPreparer(object):
             return ident
 
     def format_sequence(self, sequence, use_schema=True):
-        name = self.quote(sequence.name, sequence.quote)
-        if not self.omit_schema and use_schema and \
-            sequence.schema is not None:
-            name = self.quote_schema(sequence.schema, sequence.quote) + \
-                        "." + name
+        name = self.quote(sequence.name)
+        if not self.omit_schema and use_schema and sequence.schema is not None:
+            name = self.quote_schema(sequence.schema) + "." + name
         return name
 
     def format_label(self, label, name=None):
-        return self.quote(name or label.name, label.quote)
+        return self.quote(name or label.name)
 
     def format_alias(self, alias, name=None):
-        return self.quote(name or alias.name, alias.quote)
+        return self.quote(name or alias.name)
 
     def format_savepoint(self, savepoint, name=None):
-        return self.quote(name or savepoint.ident, savepoint.quote)
+        return self.quote(name or savepoint.ident)
 
     def format_constraint(self, constraint):
-        return self.quote(constraint.name, constraint.quote)
+        return self.quote(constraint.name)
 
     def format_table(self, table, use_schema=True, name=None):
         """Prepare a quoted table and schema name."""
 
         if name is None:
             name = table.name
-        result = self.quote(name, table.quote)
+        result = self.quote(name)
         if not self.omit_schema and use_schema \
             and getattr(table, "schema", None):
-            result = self.quote_schema(table.schema, table.quote_schema) + \
-                                "." + result
+            result = self.quote_schema(table.schema) + "." + result
         return result
 
-    def format_schema(self, name, quote):
+    def format_schema(self, name, quote=None):
         """Prepare a quoted schema name."""
 
         return self.quote(name, quote)
@@ -2784,10 +2778,9 @@ class IdentifierPreparer(object):
             if use_table:
                 return self.format_table(
                             column.table, use_schema=False,
-                            name=table_name) + "." + \
-                            self.quote(name, column.quote)
+                            name=table_name) + "." + self.quote(name)
             else:
-                return self.quote(name, column.quote)
+                return self.quote(name)
         else:
             # literal textual elements get stuck into ColumnClause a lot,
             # which shouldn't get quoted
@@ -2807,7 +2800,7 @@ class IdentifierPreparer(object):
 
         if not self.omit_schema and use_schema and \
                 getattr(table, 'schema', None):
-            return (self.quote_schema(table.schema, table.quote_schema),
+            return (self.quote_schema(table.schema),
                     self.format_table(table, use_schema=False))
         else:
             return (self.format_table(table, use_schema=False), )
index 17fb40628d18a8a0ca262eafdae6fa380cab0b99..99dd193f3f910b7690f0997a422a09f9b4129aaa 100644 (file)
@@ -530,7 +530,6 @@ class ColumnElement(ClauseElement, operators.ColumnOperators):
     __visit_name__ = 'column'
     primary_key = False
     foreign_keys = []
-    quote = None
     _label = None
     _key_label = None
     _alt_names = ()
@@ -693,7 +692,6 @@ class BindParameter(ColumnElement):
     """
 
     __visit_name__ = 'bindparam'
-    quote = None
 
     _is_crud = False
 
@@ -778,6 +776,8 @@ class BindParameter(ColumnElement):
         if value is NO_ARG:
             value = None
 
+        if quote is not None:
+            key = quoted_name(key, quote)
 
         if unique:
             self.key = _anonymous_label('%%(%d %s)s' % (id(self), key
@@ -800,7 +800,6 @@ class BindParameter(ColumnElement):
         self.callable = callable_
         self.isoutparam = isoutparam
         self.required = required
-        self.quote = quote
         if type_ is None:
             if _compared_to_type is not None:
                 self.type = \
@@ -1838,7 +1837,6 @@ class Label(ColumnElement):
         self.key = self._label = self._key_label = self.name
         self._element = element
         self._type = type_
-        self.quote = element.quote
         self._proxies = [element]
 
     @util.memoized_property
@@ -2027,6 +2025,12 @@ class ColumnClause(Immutable, ColumnElement):
             else:
                 label = t.name + "_" + name
 
+            # propagate name quoting rules for labels.
+            if getattr(name, "quote", None) is not None:
+                label = quoted_name(label, name.quote)
+            elif getattr(t.name, "quote", None) is not None:
+                label = quoted_name(label, t.name.quote)
+
             # ensure the label name doesn't conflict with that
             # of an existing column
             if label in t.c:
@@ -2078,7 +2082,6 @@ class _IdentifiedClause(Executable, ClauseElement):
     __visit_name__ = 'identified'
     _execution_options = \
         Executable._execution_options.union({'autocommit': False})
-    quote = None
 
     def __init__(self, ident):
         self.ident = ident
@@ -2096,10 +2099,92 @@ class ReleaseSavepointClause(_IdentifiedClause):
     __visit_name__ = 'release_savepoint'
 
 
-class _truncated_label(util.text_type):
+class quoted_name(util.text_type):
+    """Represent a SQL identifier combined with quoting preferences.
+
+    :class:`.quoted_name` is a Python unicode/str subclass which
+    represents a particular identifier name along with a
+    ``quote`` flag.  This ``quote`` flag, when set to
+    ``True`` or ``False``, overrides automatic quoting behavior
+    for this identifier in order to either unconditionally quote
+    or to not quote the name.  If left at its default of ``None``,
+    quoting behavior is applied to the identifier on a per-backend basis
+    based on an examination of the token itself.
+
+    A :class:`.quoted_name` object with ``quote=True`` is also
+    prevented from being modified in the case of a so-called
+    "name normalize" option.  Certain database backends, such as
+    Oracle, Firebird, and DB2 "normalize" case-insensitive names
+    as uppercase.  The SQLAlchemy dialects for these backends
+    convert from SQLAlchemy's lower-case-means-insensitive convention
+    to the upper-case-means-insensitive conventions of those backends.
+    The ``quote=True`` flag here will prevent this conversion from occurring
+    to support an identifier that's quoted as all lower case against
+    such a backend.
+
+    The :class:`.quoted_name` object is normally created automatically
+    when specifying the name for key schema constructs such as :class:`.Table`,
+    :class:`.Column`, and others.   The class can also be passed explicitly
+    as the name to any function that receives a name which can be quoted.
+    Such as to use the :meth:`.Engine.has_table` method with an unconditionally
+    quoted name::
+
+        from sqlaclchemy import create_engine
+        from sqlalchemy.sql.elements import quoted_name
+
+        engine = create_engine("oracle+cx_oracle://some_dsn")
+        engine.has_table(quoted_name("some_table", True))
+
+    The above logic will run the "has table" logic against the Oracle backend,
+    passing the name exactly as ``"some_table"`` without converting to
+    upper case.
+
+    .. versionadded:: 0.9.0
+
+    """
+
+    def __new__(cls, value, quote):
+        if value is None:
+            return None
+        elif isinstance(value, cls) and (
+                quote is None or value.quote == quote
+            ):
+            return value
+        self = super(quoted_name, cls).__new__(cls, value)
+        self.quote = quote
+        return self
+
+    def __reduce__(self):
+        return quoted_name, (util.text_type(self), self.quote)
+
+    @util.memoized_instancemethod
+    def lower(self):
+        if self.quote:
+            return self
+        else:
+            return util.text_type(self).lower()
+
+    @util.memoized_instancemethod
+    def upper(self):
+        if self.quote:
+            return self
+        else:
+            return util.text_type(self).upper()
+
+    def __repr__(self):
+        return "'%s'" % self
+
+class _truncated_label(quoted_name):
     """A unicode subclass used to identify symbolic "
     "names that may require truncation."""
 
+    def __new__(cls, value, quote=None):
+        quote = getattr(value, "quote", quote)
+        return super(_truncated_label, cls).__new__(cls, value, quote)
+
+    def __reduce__(self):
+        return self.__class__, (util.text_type(self), self.quote)
+
     def apply_map(self, map_):
         return self
 
@@ -2116,16 +2201,25 @@ class _anonymous_label(_truncated_label):
 
     def __add__(self, other):
         return _anonymous_label(
-                    util.text_type(self) +
-                    util.text_type(other))
+                    quoted_name(
+                        util.text_type.__add__(self, util.text_type(other)),
+                        self.quote)
+                )
 
     def __radd__(self, other):
         return _anonymous_label(
-                    util.text_type(other) +
-                    util.text_type(self))
+                    quoted_name(
+                        util.text_type.__add__(util.text_type(other), self),
+                        self.quote)
+                    )
 
     def apply_map(self, map_):
-        return self % map_
+        if self.quote is not None:
+            # preserve quoting only if necessary
+            return quoted_name(self % map_, self.quote)
+        else:
+            # else skip the constructor call
+            return self % map_
 
 
 def _as_truncated(value):
index ca83236c756169308f44e5564d993a4d94866054..b190c3874d93bc822bcbdfa568d6b378bb09ce15 100644 (file)
@@ -37,7 +37,7 @@ from . import type_api
 from .base import _bind_or_error, ColumnCollection
 from .elements import ClauseElement, ColumnClause, _truncated_label, \
                         _as_truncated, TextClause, _literal_as_text,\
-                        ColumnElement, _find_columns
+                        ColumnElement, _find_columns, quoted_name
 from .selectable import TableClause
 import collections
 import sqlalchemy
@@ -67,7 +67,6 @@ class SchemaItem(SchemaEventTarget, visitors.Visitable):
     """Base class for items that define a database schema."""
 
     __visit_name__ = 'schema_item'
-    quote = None
 
     def _init_items(self, *args):
         """Initialize the list of child items for this SchemaItem."""
@@ -83,6 +82,17 @@ class SchemaItem(SchemaEventTarget, visitors.Visitable):
     def __repr__(self):
         return util.generic_repr(self)
 
+    @property
+    @util.deprecated('0.9', 'Use ``<obj>.name.quote``')
+    def quote(self):
+        """Return the value of the ``quote`` flag passed
+        to this schema object, for those schema items which
+        have a ``name`` field.
+
+        """
+
+        return self.name.quote
+
     @util.memoized_property
     def info(self):
         """Info dictionary associated with the object, allowing user-defined
@@ -114,25 +124,29 @@ class Table(SchemaItem, TableClause):
     a second time will return the *same* :class:`.Table` object - in this way
     the :class:`.Table` constructor acts as a registry function.
 
-    See also:
+    .. seealso::
 
-    :ref:`metadata_describing` - Introduction to database metadata
+        :ref:`metadata_describing` - Introduction to database metadata
 
     Constructor arguments are as follows:
 
     :param name: The name of this table as represented in the database.
 
-        This property, along with the *schema*, indicates the *singleton
-        identity* of this table in relation to its parent :class:`.MetaData`.
+        The table name, along with the value of the ``schema`` parameter,
+        forms a key which uniquely identifies this :class:`.Table` within
+        the owning :class:`.MetaData` collection.
         Additional calls to :class:`.Table` with the same name, metadata,
         and schema name will return the same :class:`.Table` object.
 
         Names which contain no upper case characters
         will be treated as case insensitive names, and will not be quoted
-        unless they are a reserved word.  Names with any number of upper
-        case characters will be quoted and sent exactly.  Note that this
-        behavior applies even for databases which standardize upper
-        case names as case insensitive such as Oracle.
+        unless they are a reserved word or contain special characters.
+        A name with any number of upper case characters is considered
+        to be case sensitive, and will be sent as quoted.
+
+        To enable unconditional quoting for the table name, specify the flag
+        ``quote=True`` to the constructor, or use the :class:`.quoted_name`
+        construct to specify the name.
 
     :param metadata: a :class:`.MetaData` object which will contain this
         table.  The metadata is used as a point of association of this table
@@ -263,9 +277,17 @@ class Table(SchemaItem, TableClause):
 
     :param quote_schema: same as 'quote' but applies to the schema identifier.
 
-    :param schema: The *schema name* for this table, which is required if
+    :param schema: The schema name for this table, which is required if
         the table resides in a schema other than the default selected schema
-        for the engine's database connection. Defaults to ``None``.
+        for the engine's database connection.  Defaults to ``None``.
+
+        The quoting rules for the schema name are the same as those for the
+        ``name`` parameter, in that quoting is applied for reserved words or
+        case-sensitive names; to enable unconditional quoting for the
+        schema name, specify the flag
+        ``quote_schema=True`` to the constructor, or use the :class:`.quoted_name`
+        construct to specify the name.
+
 
     :param useexisting: Deprecated.  Use extend_existing.
 
@@ -329,6 +351,15 @@ class Table(SchemaItem, TableClause):
                 #metadata._remove_table(name, schema)
                 raise
 
+
+    @property
+    @util.deprecated('0.9', 'Use ``table.schema.quote``')
+    def quote_schema(self):
+        """Return the value of the ``quote_schema`` flag passed
+        to this :class:`.Table`."""
+
+        return self.schema.quote
+
     def __init__(self, *args, **kw):
         """Constructor for :class:`~.schema.Table`.
 
@@ -341,15 +372,15 @@ class Table(SchemaItem, TableClause):
         # calling the superclass constructor.
 
     def _init(self, name, metadata, *args, **kwargs):
-        super(Table, self).__init__(name)
+        super(Table, self).__init__(quoted_name(name, kwargs.pop('quote', None)))
         self.metadata = metadata
+
         self.schema = kwargs.pop('schema', None)
         if self.schema is None:
             self.schema = metadata.schema
-            self.quote_schema = kwargs.pop(
-                'quote_schema', metadata.quote_schema)
         else:
-            self.quote_schema = kwargs.pop('quote_schema', None)
+            quote_schema = kwargs.pop('quote_schema', None)
+            self.schema = quoted_name(self.schema, quote_schema)
 
         self.indexes = set()
         self.constraints = set()
@@ -370,7 +401,7 @@ class Table(SchemaItem, TableClause):
         include_columns = kwargs.pop('include_columns', None)
 
         self.implicit_returning = kwargs.pop('implicit_returning', True)
-        self.quote = kwargs.pop('quote', None)
+
         if 'info' in kwargs:
             self.info = kwargs.pop('info')
         if 'listeners' in kwargs:
@@ -444,7 +475,8 @@ class Table(SchemaItem, TableClause):
 
         for key in ('quote', 'quote_schema'):
             if key in kwargs:
-                setattr(self, key, kwargs.pop(key))
+                raise exc.ArgumentError(
+                    "Can't redefine 'quote' or 'quote_schema' arguments")
 
         if 'info' in kwargs:
             self.info = kwargs.pop('info')
@@ -597,7 +629,9 @@ class Table(SchemaItem, TableClause):
         :class:`.Table`, using the given :class:`.Connectable`
         for connectivity.
 
-        See also :meth:`.MetaData.create_all`.
+        .. seealso::
+
+            :meth:`.MetaData.create_all`.
 
         """
 
@@ -612,7 +646,9 @@ class Table(SchemaItem, TableClause):
         :class:`.Table`, using the given :class:`.Connectable`
         for connectivity.
 
-        See also :meth:`.MetaData.drop_all`.
+        .. seealso::
+
+            :meth:`.MetaData.drop_all`.
 
         """
         if bind is None:
@@ -925,6 +961,12 @@ class Column(SchemaItem, ColumnClause):
                         "May not pass type_ positionally and as a keyword.")
                 type_ = args.pop(0)
 
+        if name is not None:
+            name = quoted_name(name, kwargs.pop('quote', None))
+        elif "quote" in kwargs:
+            raise exc.ArgumentError("Explicit 'name' is required when "
+                            "sending 'quote' argument")
+
         super(Column, self).__init__(name, type_)
         self.key = kwargs.pop('key', name)
         self.primary_key = kwargs.pop('primary_key', False)
@@ -935,7 +977,6 @@ class Column(SchemaItem, ColumnClause):
         self.index = kwargs.pop('index', None)
         self.unique = kwargs.pop('unique', None)
         self.system = kwargs.pop('system', False)
-        self.quote = kwargs.pop('quote', None)
         self.doc = kwargs.pop('doc', None)
         self.onupdate = kwargs.pop('onupdate', None)
         self.autoincrement = kwargs.pop('autoincrement', True)
@@ -988,6 +1029,10 @@ class Column(SchemaItem, ColumnClause):
             raise exc.ArgumentError(
                 "Unknown arguments passed to Column: " + repr(list(kwargs)))
 
+#    @property
+#    def quote(self):
+#        return getattr(self.name, "quote", None)
+
     def __str__(self):
         if self.name is None:
             return "(no name)"
@@ -1123,7 +1168,7 @@ class Column(SchemaItem, ColumnClause):
                 nullable=self.nullable,
                 unique=self.unique,
                 system=self.system,
-                quote=self.quote,
+                #quote=self.quote,
                 index=self.index,
                 autoincrement=self.autoincrement,
                 default=self.default,
@@ -1161,7 +1206,6 @@ class Column(SchemaItem, ColumnClause):
                 key=key if key else name if name else self.key,
                 primary_key=self.primary_key,
                 nullable=self.nullable,
-                quote=self.quote,
                 _proxies=[self], *fk)
         except TypeError:
             util.raise_from_cause(
@@ -1791,7 +1835,11 @@ class Sequence(DefaultGenerator):
     be emitted as well.   For platforms that don't support sequences,
     the :class:`.Sequence` construct is ignored.
 
-    See also: :class:`.CreateSequence` :class:`.DropSequence`
+    .. seealso::
+
+        :class:`.CreateSequence`
+
+        :class:`.DropSequence`
 
     """
 
@@ -1828,6 +1876,8 @@ class Sequence(DefaultGenerator):
          forces quoting of the schema name on or off.  When left at its
          default of ``None``, normal quoting rules based on casing and reserved
          words take place.
+        :param quote_schema: set the quoting preferences for the ``schema``
+         name.
         :param metadata: optional :class:`.MetaData` object which will be
          associated with this :class:`.Sequence`.  A :class:`.Sequence`
          that is associated with a :class:`.MetaData` gains access to the
@@ -1855,17 +1905,14 @@ class Sequence(DefaultGenerator):
 
         """
         super(Sequence, self).__init__(for_update=for_update)
-        self.name = name
+        self.name = quoted_name(name, quote)
         self.start = start
         self.increment = increment
         self.optional = optional
-        self.quote = quote
         if metadata is not None and schema is None and metadata.schema:
             self.schema = schema = metadata.schema
-            self.quote_schema = metadata.quote_schema
         else:
-            self.schema = schema
-            self.quote_schema = quote_schema
+            self.schema = quoted_name(schema, quote_schema)
         self.metadata = metadata
         self._key = _get_table_key(name, schema)
         if metadata:
@@ -2556,7 +2603,7 @@ class Index(ColumnCollectionMixin, SchemaItem):
         # objects are present
         ColumnCollectionMixin.__init__(self, *columns)
 
-        self.name = name
+        self.name = quoted_name(name, kw.pop("quote", None))
         self.unique = kw.pop('unique', False)
         self.kwargs = kw
 
@@ -2598,7 +2645,9 @@ class Index(ColumnCollectionMixin, SchemaItem):
         :class:`.Index`, using the given :class:`.Connectable`
         for connectivity.
 
-        See also :meth:`.MetaData.create_all`.
+        .. seealso::
+
+            :meth:`.MetaData.create_all`.
 
         """
         if bind is None:
@@ -2611,7 +2660,9 @@ class Index(ColumnCollectionMixin, SchemaItem):
         :class:`.Index`, using the given :class:`.Connectable`
         for connectivity.
 
-        See also :meth:`.MetaData.drop_all`.
+        .. seealso::
+
+            :meth:`.MetaData.drop_all`.
 
         """
         if bind is None:
@@ -2653,12 +2704,9 @@ class MetaData(SchemaItem):
     MetaData is a thread-safe object after tables have been explicitly defined
     or loaded via reflection.
 
-    See also:
-
-    :ref:`metadata_describing` - Introduction to database metadata
+    .. seealso::
 
-    .. index::
-      single: thread safety; MetaData
+        :ref:`metadata_describing` - Introduction to database metadata
 
     """
 
@@ -2695,8 +2743,7 @@ class MetaData(SchemaItem):
 
         """
         self.tables = util.immutabledict()
-        self.schema = schema
-        self.quote_schema = quote_schema
+        self.schema = quoted_name(schema, quote_schema)
         self._schemas = set()
         self._sequences = {}
         self._fk_memos = collections.defaultdict(list)
@@ -2742,7 +2789,6 @@ class MetaData(SchemaItem):
     def __getstate__(self):
         return {'tables': self.tables,
                 'schema': self.schema,
-                'quote_schema': self.quote_schema,
                 'schemas': self._schemas,
                 'sequences': self._sequences,
                 'fk_memos': self._fk_memos}
@@ -2750,7 +2796,6 @@ class MetaData(SchemaItem):
     def __setstate__(self, state):
         self.tables = state['tables']
         self.schema = state['schema']
-        self.quote_schema = state['quote_schema']
         self._bind = None
         self._sequences = state['sequences']
         self._schemas = state['schemas']
index c32de77ea0ad6ddd212fdb4ee47257941992e11d..e06262c6deb71e9d5f75cd1cff5e19a097ca16a9 100644 (file)
@@ -136,7 +136,6 @@ class FromClause(Selectable):
     __visit_name__ = 'fromclause'
     named_with_column = False
     _hide_froms = []
-    quote = None
     schema = None
     _memoized_property = util.group_expirable_memoized_property(["_columns"])
 
index 38e0d1bd343b696330fc5f58e884148947ffba1d..db0ad248c3c91aef5840a8d7e928ed8fa4ee7515 100644 (file)
@@ -12,6 +12,7 @@ import datetime as dt
 import codecs
 
 from .type_api import TypeEngine, TypeDecorator, to_instance
+from .elements import quoted_name
 from .default_comparator import _DefaultColumnComparator
 from .. import exc, util, processors
 from .base import _bind_or_error, SchemaEventTarget
@@ -840,8 +841,11 @@ class SchemaType(SchemaEventTarget):
     """
 
     def __init__(self, **kw):
-        self.name = kw.pop('name', None)
-        self.quote = kw.pop('quote', None)
+        name = kw.pop('name', None)
+        if name is not None:
+            self.name = quoted_name(name, kw.pop('quote', None))
+        else:
+            self.name = None
         self.schema = kw.pop('schema', None)
         self.metadata = kw.pop('metadata', None)
         self.inherit_schema = kw.pop('inherit_schema', False)
@@ -896,7 +900,6 @@ class SchemaType(SchemaEventTarget):
         schema = kw.pop('schema', self.schema)
         metadata = kw.pop('metadata', self.metadata)
         return impltype(name=self.name,
-                    quote=self.quote,
                     schema=schema,
                     metadata=metadata,
                     inherit_schema=self.inherit_schema,
@@ -1008,10 +1011,7 @@ class Enum(String, SchemaType):
                 owning :class:`.Table`.  If this behavior is desired,
                 set the ``inherit_schema`` flag to ``True``.
 
-        :param quote: Force quoting to be on or off on the type's name. If
-           left as the default of `None`, the usual schema-level "case
-           sensitive"/"reserved name" rules are used to determine if this
-           type's name should be quoted.
+        :param quote: Set explicit quoting preferences for the type's name.
 
         :param inherit_schema: When ``True``, the "schema" from the owning
            :class:`.Table` will be copied to the "schema" attribute of this
@@ -1071,7 +1071,6 @@ class Enum(String, SchemaType):
         metadata = kw.pop('metadata', self.metadata)
         if issubclass(impltype, Enum):
             return impltype(name=self.name,
-                        quote=self.quote,
                         schema=schema,
                         metadata=metadata,
                         convert_unicode=self.convert_unicode,
index b927f1b3cc9b1880ecff73dbcc948337797afda8..2a5c2e2774db7a22c97aa1ab3876aa88fd365662 100644 (file)
@@ -269,7 +269,6 @@ def expression_as_ddl(clause):
         elif isinstance(element, ColumnClause) and \
                 element.table is not None:
             col = ColumnClause(element.name)
-            col.quote = element.quote
             return col
         else:
             return None
index 2de0032ddc6decce3af47750e9c6bda0599a38a1..8917d77725fbee5287a19675c3e6c3520eeb7153 100644 (file)
@@ -1276,8 +1276,10 @@ class DeclarativeTest(DeclarativeTestBase):
         # case
 
         sa.orm.configure_mappers()
-        eq_(str(list(Address.user_id.property.columns[0].foreign_keys)[0]),
-            "ForeignKey('users.id')")
+        eq_(
+            list(Address.user_id.property.columns[0].foreign_keys)[0].column,
+            User.__table__.c.id
+        )
         Base.metadata.create_all()
         u1 = User(name='u1', addresses=[Address(email='one'),
                   Address(email='two')])
index 4a392d2081371bb311a97d3b8667d257f5f05464..b3c83391bb820bae452ac1e7042b056dbb322a9d 100644 (file)
@@ -16,6 +16,7 @@
 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.6_sqlite_pysqlite_nocextensions 72
 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_mysql_mysqldb_cextensions 72
 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_mysql_mysqldb_nocextensions 72
+test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_mysql_oursql_nocextensions 72
 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_oracle_cx_oracle_nocextensions 72
 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_postgresql_psycopg2_cextensions 72
 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_postgresql_psycopg2_nocextensions 72
@@ -23,7 +24,10 @@ test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_sqlite_pysqlite_cex
 test.aaa_profiling.test_compiler.CompileTest.test_insert 2.7_sqlite_pysqlite_nocextensions 72
 test.aaa_profiling.test_compiler.CompileTest.test_insert 3.2_postgresql_psycopg2_nocextensions 74
 test.aaa_profiling.test_compiler.CompileTest.test_insert 3.2_sqlite_pysqlite_nocextensions 74
+test.aaa_profiling.test_compiler.CompileTest.test_insert 3.3_mysql_oursql_cextensions 77
+test.aaa_profiling.test_compiler.CompileTest.test_insert 3.3_mysql_oursql_nocextensions 77
 test.aaa_profiling.test_compiler.CompileTest.test_insert 3.3_oracle_cx_oracle_nocextensions 76
+test.aaa_profiling.test_compiler.CompileTest.test_insert 3.3_postgresql_psycopg2_cextensions 77
 test.aaa_profiling.test_compiler.CompileTest.test_insert 3.3_postgresql_psycopg2_nocextensions 74
 test.aaa_profiling.test_compiler.CompileTest.test_insert 3.3_sqlite_pysqlite_cextensions 76
 test.aaa_profiling.test_compiler.CompileTest.test_insert 3.3_sqlite_pysqlite_nocextensions 74
@@ -33,68 +37,67 @@ test.aaa_profiling.test_compiler.CompileTest.test_insert 3.3_sqlite_pysqlite_noc
 test.aaa_profiling.test_compiler.CompileTest.test_select 2.6_sqlite_pysqlite_nocextensions 141
 test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_mysql_mysqldb_cextensions 141
 test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_mysql_mysqldb_nocextensions 141
-test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_oracle_cx_oracle_nocextensions 141
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_mysql_oursql_nocextensions 148
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_oracle_cx_oracle_nocextensions 148
 test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_cextensions 141
 test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_nocextensions 141
 test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_cextensions 141
 test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_nocextensions 141
-test.aaa_profiling.test_compiler.CompileTest.test_select 3.2_postgresql_psycopg2_nocextensions 151
-test.aaa_profiling.test_compiler.CompileTest.test_select 3.2_sqlite_pysqlite_nocextensions 151
-test.aaa_profiling.test_compiler.CompileTest.test_select 3.3_oracle_cx_oracle_nocextensions 153
-test.aaa_profiling.test_compiler.CompileTest.test_select 3.3_postgresql_psycopg2_nocextensions 151
-test.aaa_profiling.test_compiler.CompileTest.test_select 3.3_sqlite_pysqlite_cextensions 157
-test.aaa_profiling.test_compiler.CompileTest.test_select 3.3_sqlite_pysqlite_nocextensions 151
+test.aaa_profiling.test_compiler.CompileTest.test_select 3.3_mysql_oursql_cextensions 163
+test.aaa_profiling.test_compiler.CompileTest.test_select 3.3_mysql_oursql_nocextensions 163
+test.aaa_profiling.test_compiler.CompileTest.test_select 3.3_postgresql_psycopg2_cextensions 163
+test.aaa_profiling.test_compiler.CompileTest.test_select 3.3_postgresql_psycopg2_nocextensions 163
+test.aaa_profiling.test_compiler.CompileTest.test_select 3.3_sqlite_pysqlite_cextensions 163
 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_select_labels
 
 test.aaa_profiling.test_compiler.CompileTest.test_select_labels 2.6_sqlite_pysqlite_nocextensions 175
 test.aaa_profiling.test_compiler.CompileTest.test_select_labels 2.7_mysql_mysqldb_cextensions 175
 test.aaa_profiling.test_compiler.CompileTest.test_select_labels 2.7_mysql_mysqldb_nocextensions 175
+test.aaa_profiling.test_compiler.CompileTest.test_select_labels 2.7_mysql_oursql_nocextensions 181
 test.aaa_profiling.test_compiler.CompileTest.test_select_labels 2.7_oracle_cx_oracle_nocextensions 175
 test.aaa_profiling.test_compiler.CompileTest.test_select_labels 2.7_postgresql_psycopg2_cextensions 175
 test.aaa_profiling.test_compiler.CompileTest.test_select_labels 2.7_postgresql_psycopg2_nocextensions 175
 test.aaa_profiling.test_compiler.CompileTest.test_select_labels 2.7_sqlite_pysqlite_cextensions 175
 test.aaa_profiling.test_compiler.CompileTest.test_select_labels 2.7_sqlite_pysqlite_nocextensions 175
-test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.2_postgresql_psycopg2_nocextensions 185
-test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.2_sqlite_pysqlite_nocextensions 185
-test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.3_oracle_cx_oracle_nocextensions 187
-test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.3_postgresql_psycopg2_nocextensions 185
-test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.3_sqlite_pysqlite_cextensions 191
-test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.3_sqlite_pysqlite_nocextensions 185
+test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.3_mysql_oursql_cextensions 196
+test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.3_mysql_oursql_nocextensions 196
+test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.3_postgresql_psycopg2_cextensions 196
+test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.3_postgresql_psycopg2_nocextensions 196
+test.aaa_profiling.test_compiler.CompileTest.test_select_labels 3.3_sqlite_pysqlite_cextensions 196
 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_update
 
 test.aaa_profiling.test_compiler.CompileTest.test_update 2.6_sqlite_pysqlite_nocextensions 75
 test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_mysql_mysqldb_cextensions 75
 test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_mysql_mysqldb_nocextensions 75
+test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_mysql_oursql_nocextensions 77
 test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_oracle_cx_oracle_nocextensions 75
 test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_postgresql_psycopg2_cextensions 75
 test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_postgresql_psycopg2_nocextensions 75
 test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_sqlite_pysqlite_cextensions 75
 test.aaa_profiling.test_compiler.CompileTest.test_update 2.7_sqlite_pysqlite_nocextensions 75
-test.aaa_profiling.test_compiler.CompileTest.test_update 3.2_postgresql_psycopg2_nocextensions 75
-test.aaa_profiling.test_compiler.CompileTest.test_update 3.2_sqlite_pysqlite_nocextensions 75
-test.aaa_profiling.test_compiler.CompileTest.test_update 3.3_oracle_cx_oracle_nocextensions 77
-test.aaa_profiling.test_compiler.CompileTest.test_update 3.3_postgresql_psycopg2_nocextensions 75
-test.aaa_profiling.test_compiler.CompileTest.test_update 3.3_sqlite_pysqlite_cextensions 77
-test.aaa_profiling.test_compiler.CompileTest.test_update 3.3_sqlite_pysqlite_nocextensions 75
+test.aaa_profiling.test_compiler.CompileTest.test_update 3.3_mysql_oursql_cextensions 80
+test.aaa_profiling.test_compiler.CompileTest.test_update 3.3_mysql_oursql_nocextensions 80
+test.aaa_profiling.test_compiler.CompileTest.test_update 3.3_postgresql_psycopg2_cextensions 80
+test.aaa_profiling.test_compiler.CompileTest.test_update 3.3_postgresql_psycopg2_nocextensions 80
+test.aaa_profiling.test_compiler.CompileTest.test_update 3.3_sqlite_pysqlite_cextensions 80
 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause
 
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.6_sqlite_pysqlite_nocextensions 137
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_mysql_mysqldb_cextensions 137
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_mysql_mysqldb_nocextensions 137
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_oracle_cx_oracle_nocextensions 137
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_postgresql_psycopg2_cextensions 137
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_postgresql_psycopg2_nocextensions 137
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_sqlite_pysqlite_cextensions 137
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_sqlite_pysqlite_nocextensions 137
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.2_postgresql_psycopg2_nocextensions 136
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.2_sqlite_pysqlite_nocextensions 136
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.3_oracle_cx_oracle_nocextensions 138
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.3_postgresql_psycopg2_nocextensions 136
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.3_sqlite_pysqlite_cextensions 143
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.3_sqlite_pysqlite_nocextensions 136
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_mysql_mysqldb_cextensions 149
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_mysql_mysqldb_nocextensions 149
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_mysql_oursql_nocextensions 149
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_oracle_cx_oracle_nocextensions 149
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_postgresql_psycopg2_cextensions 149
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_postgresql_psycopg2_nocextensions 149
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_sqlite_pysqlite_cextensions 149
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 2.7_sqlite_pysqlite_nocextensions 149
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.3_mysql_oursql_cextensions 151
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.3_mysql_oursql_nocextensions 151
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.3_postgresql_psycopg2_cextensions 151
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.3_postgresql_psycopg2_nocextensions 151
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.3_sqlite_pysqlite_cextensions 151
 
 # TEST: test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline
 
@@ -104,6 +107,8 @@ test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_postgresql_psycop
 test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_postgresql_psycopg2_nocextensions 51049
 test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_cextensions 30008
 test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 2.7_sqlite_pysqlite_nocextensions 39025
+test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.3_postgresql_psycopg2_cextensions 32141
+test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.3_postgresql_psycopg2_nocextensions 41144
 test.aaa_profiling.test_orm.DeferOptionsTest.test_baseline 3.3_sqlite_pysqlite_cextensions 31190
 
 # TEST: test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols
@@ -114,6 +119,8 @@ test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_postgresql
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_postgresql_psycopg2_nocextensions 32835
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_cextensions 29812
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 2.7_sqlite_pysqlite_nocextensions 32817
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.3_postgresql_psycopg2_cextensions 31858
+test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.3_postgresql_psycopg2_nocextensions 34861
 test.aaa_profiling.test_orm.DeferOptionsTest.test_defer_many_cols 3.3_sqlite_pysqlite_cextensions 30960
 
 # TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity
@@ -129,6 +136,7 @@ test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.2_postgresql_psycopg2_nocextensions 18987
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.2_sqlite_pysqlite_nocextensions 18987
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.3_oracle_cx_oracle_nocextensions 18987
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.3_postgresql_psycopg2_cextensions 18987
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.3_postgresql_psycopg2_nocextensions 18987
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.3_sqlite_pysqlite_cextensions 18987
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_identity 3.3_sqlite_pysqlite_nocextensions 18987
@@ -146,6 +154,7 @@ test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.2_postgresql_psycopg2_nocextensions 121790
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.2_sqlite_pysqlite_nocextensions 121822
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.3_oracle_cx_oracle_nocextensions 130792
+test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.3_postgresql_psycopg2_cextensions 126077
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.3_postgresql_psycopg2_nocextensions 121822
 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.3_sqlite_pysqlite_cextensions 164074
 
@@ -161,6 +170,7 @@ test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_nocextensions 21790
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.2_postgresql_psycopg2_nocextensions 20424
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.3_oracle_cx_oracle_nocextensions 21244
+test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.3_postgresql_psycopg2_cextensions 20268
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.3_postgresql_psycopg2_nocextensions 20344
 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.3_sqlite_pysqlite_cextensions 23404
 
@@ -176,6 +186,7 @@ test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_cexten
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_nocextensions 1521
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.2_postgresql_psycopg2_nocextensions 1332
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.3_oracle_cx_oracle_nocextensions 1366
+test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.3_postgresql_psycopg2_cextensions 1358
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.3_postgresql_psycopg2_nocextensions 1357
 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.3_sqlite_pysqlite_cextensions 1598
 
@@ -192,6 +203,7 @@ test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 2.7_sqlite_pysqlite_noc
 test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.2_postgresql_psycopg2_nocextensions 127,19
 test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.2_sqlite_pysqlite_nocextensions 127,19
 test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.3_oracle_cx_oracle_nocextensions 134,19
+test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.3_postgresql_psycopg2_cextensions 132,20
 test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.3_postgresql_psycopg2_nocextensions 127,19
 test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.3_sqlite_pysqlite_cextensions 134,19
 test.aaa_profiling.test_orm.MergeTest.test_merge_no_load 3.3_sqlite_pysqlite_nocextensions 127,19
@@ -209,6 +221,7 @@ test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 2.7_sqlite_pysqlit
 test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 3.2_postgresql_psycopg2_nocextensions 75
 test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 3.2_sqlite_pysqlite_nocextensions 75
 test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 3.3_oracle_cx_oracle_nocextensions 74
+test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 3.3_postgresql_psycopg2_cextensions 74
 test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 3.3_postgresql_psycopg2_nocextensions 74
 test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 3.3_sqlite_pysqlite_cextensions 74
 test.aaa_profiling.test_pool.QueuePoolTest.test_first_connect 3.3_sqlite_pysqlite_nocextensions 74
@@ -226,6 +239,7 @@ test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 2.7_sqlite_pysqli
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 3.2_postgresql_psycopg2_nocextensions 23
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 3.2_sqlite_pysqlite_nocextensions 23
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 3.3_oracle_cx_oracle_nocextensions 22
+test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 3.3_postgresql_psycopg2_cextensions 23
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 3.3_postgresql_psycopg2_nocextensions 22
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 3.3_sqlite_pysqlite_cextensions 23
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_connect 3.3_sqlite_pysqlite_nocextensions 22
@@ -243,6 +257,7 @@ test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 2.7_sq
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 3.2_postgresql_psycopg2_nocextensions 8
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 3.2_sqlite_pysqlite_nocextensions 8
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 3.3_oracle_cx_oracle_nocextensions 8
+test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 3.3_postgresql_psycopg2_cextensions 8
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 3.3_postgresql_psycopg2_nocextensions 8
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 3.3_sqlite_pysqlite_cextensions 8
 test.aaa_profiling.test_pool.QueuePoolTest.test_second_samethread_connect 3.3_sqlite_pysqlite_nocextensions 8
@@ -260,6 +275,7 @@ test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 3.2_postgresql_psycopg2_nocextensions 41
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 3.2_sqlite_pysqlite_nocextensions 41
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 3.3_oracle_cx_oracle_nocextensions 41
+test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 3.3_postgresql_psycopg2_cextensions 41
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 3.3_postgresql_psycopg2_nocextensions 41
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 3.3_sqlite_pysqlite_cextensions 41
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_connection_execute 3.3_sqlite_pysqlite_nocextensions 41
@@ -277,6 +293,7 @@ test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 2.7_
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 3.2_postgresql_psycopg2_nocextensions 71
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 3.2_sqlite_pysqlite_nocextensions 71
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 3.3_oracle_cx_oracle_nocextensions 71
+test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 3.3_postgresql_psycopg2_cextensions 71
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 3.3_postgresql_psycopg2_nocextensions 71
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 3.3_sqlite_pysqlite_cextensions 71
 test.aaa_profiling.test_resultset.ExecutionTest.test_minimal_engine_execute 3.3_sqlite_pysqlite_nocextensions 71
@@ -294,6 +311,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 3.2_postgresql_psycopg2_nocextensions 15
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 3.2_sqlite_pysqlite_nocextensions 15
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 3.3_oracle_cx_oracle_nocextensions 15
+test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 3.3_postgresql_psycopg2_cextensions 15
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 3.3_postgresql_psycopg2_nocextensions 15
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 3.3_sqlite_pysqlite_cextensions 15
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 3.3_sqlite_pysqlite_nocextensions 15
@@ -311,6 +329,7 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_string 2.7_sqlite_pysqlite_
 test.aaa_profiling.test_resultset.ResultSetTest.test_string 3.2_postgresql_psycopg2_nocextensions 14459
 test.aaa_profiling.test_resultset.ResultSetTest.test_string 3.2_sqlite_pysqlite_nocextensions 14430
 test.aaa_profiling.test_resultset.ResultSetTest.test_string 3.3_oracle_cx_oracle_nocextensions 14548
+test.aaa_profiling.test_resultset.ResultSetTest.test_string 3.3_postgresql_psycopg2_cextensions 497
 test.aaa_profiling.test_resultset.ResultSetTest.test_string 3.3_postgresql_psycopg2_nocextensions 14457
 test.aaa_profiling.test_resultset.ResultSetTest.test_string 3.3_sqlite_pysqlite_cextensions 453
 test.aaa_profiling.test_resultset.ResultSetTest.test_string 3.3_sqlite_pysqlite_nocextensions 14430
@@ -328,41 +347,46 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 2.7_sqlite_pysqlite
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.2_postgresql_psycopg2_nocextensions 14459
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.2_sqlite_pysqlite_nocextensions 14430
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.3_oracle_cx_oracle_nocextensions 14548
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.3_postgresql_psycopg2_cextensions 497
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.3_postgresql_psycopg2_nocextensions 14457
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.3_sqlite_pysqlite_cextensions 453
 test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.3_sqlite_pysqlite_nocextensions 14430
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate
 
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_cextensions 5340
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_nocextensions 5175
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 3.2_postgresql_psycopg2_nocextensions 4828
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 3.3_postgresql_psycopg2_nocextensions 4792
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 3.3_postgresql_psycopg2_cextensions 5157
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_1a_populate 3.3_postgresql_psycopg2_nocextensions 5179
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert
 
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_cextensions 256
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_nocextensions 256
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 3.3_postgresql_psycopg2_nocextensions 251
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 3.3_postgresql_psycopg2_cextensions 259
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_2_insert 3.3_postgresql_psycopg2_nocextensions 259
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties
 
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 3425
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 3625
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_nocextensions 3749
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 3.2_postgresql_psycopg2_nocextensions 3401
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 3.3_postgresql_psycopg2_nocextensions 3385
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 3.3_postgresql_psycopg2_cextensions 3569
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 3.3_postgresql_psycopg2_nocextensions 3665
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions
 
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_cextensions 11045
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_cextensions 11688
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_nocextensions 12747
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 3.2_postgresql_psycopg2_nocextensions 11849
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 3.3_postgresql_psycopg2_nocextensions 11803
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 3.3_postgresql_psycopg2_cextensions 11548
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions 3.3_postgresql_psycopg2_nocextensions 12720
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates
 
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_cextensions 1050
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_nocextensions 1167
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 3.2_postgresql_psycopg2_nocextensions 1114
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 3.3_postgresql_psycopg2_cextensions 1044
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 3.3_postgresql_psycopg2_nocextensions 1106
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing
@@ -370,20 +394,23 @@ test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_5_aggregates 3.3_postgr
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_cextensions 1811
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_nocextensions 1858
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 3.2_postgresql_psycopg2_nocextensions 1731
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 3.3_postgresql_psycopg2_nocextensions 1721
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 3.3_postgresql_psycopg2_cextensions 1846
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_6_editing 3.3_postgresql_psycopg2_nocextensions 1853
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview
 
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 2.7_postgresql_psycopg2_cextensions 2300
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 2.7_postgresql_psycopg2_nocextensions 2559
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 3.2_postgresql_psycopg2_nocextensions 2483
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 3.3_postgresql_psycopg2_nocextensions 2473
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 3.3_postgresql_psycopg2_cextensions 2460
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_7_multiview 3.3_postgresql_psycopg2_nocextensions 2652
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate
 
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_cextensions 6157
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 2.7_postgresql_psycopg2_nocextensions 6276
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 3.2_postgresql_psycopg2_nocextensions 6252
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 3.3_postgresql_psycopg2_cextensions 6286
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 3.3_postgresql_psycopg2_nocextensions 6251
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert
@@ -391,20 +418,23 @@ test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_1a_populate 3.3_pos
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_cextensions 391
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 2.7_postgresql_psycopg2_nocextensions 398
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 3.2_postgresql_psycopg2_nocextensions 395
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 3.3_postgresql_psycopg2_cextensions 391
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_2_insert 3.3_postgresql_psycopg2_nocextensions 394
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties
 
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 6422
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 6765
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_nocextensions 6654
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 3.2_postgresql_psycopg2_nocextensions 6560
-test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 3.3_postgresql_psycopg2_nocextensions 6560
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 3.3_postgresql_psycopg2_cextensions 6895
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_3_properties 3.3_postgresql_psycopg2_nocextensions 6999
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions
 
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_cextensions 19145
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 2.7_postgresql_psycopg2_nocextensions 20576
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 3.2_postgresql_psycopg2_nocextensions 20279
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 3.3_postgresql_psycopg2_cextensions 20117
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 3.3_postgresql_psycopg2_nocextensions 20279
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates
@@ -412,6 +442,7 @@ test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_4_expressions 3.3_p
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_cextensions 1063
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 2.7_postgresql_psycopg2_nocextensions 1171
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 3.2_postgresql_psycopg2_nocextensions 1120
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 3.3_postgresql_psycopg2_cextensions 1059
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 3.3_postgresql_psycopg2_nocextensions 1113
 
 # TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing
@@ -419,4 +450,5 @@ test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_5_aggregates 3.3_po
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_cextensions 2686
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 2.7_postgresql_psycopg2_nocextensions 2749
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 3.2_postgresql_psycopg2_nocextensions 2749
+test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 3.3_postgresql_psycopg2_cextensions 2796
 test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_profile_6_editing 3.3_postgresql_psycopg2_nocextensions 2749
index 851e9b9203259e196aa7126e545599dd595ed1d7..00426b227c5291465b8c22b81b3e68625cfaba49 100644 (file)
@@ -581,11 +581,13 @@ class MetaDataTest(fixtures.TestBase, ComparesTables):
                 kw['quote_schema'] = quote_schema
             t = Table(name, metadata, **kw)
             eq_(t.schema, exp_schema, "test %d, table schema" % i)
-            eq_(t.quote_schema, exp_quote_schema,
+            eq_(t.schema.quote if t.schema is not None else None,
+                            exp_quote_schema,
                             "test %d, table quote_schema" % i)
             seq = Sequence(name, metadata=metadata, **kw)
             eq_(seq.schema, exp_schema, "test %d, seq schema" % i)
-            eq_(seq.quote_schema, exp_quote_schema,
+            eq_(seq.schema.quote if seq.schema is not None else None,
+                            exp_quote_schema,
                             "test %d, seq quote_schema" % i)
 
     def test_manual_dependencies(self):
@@ -1039,7 +1041,7 @@ class UseExistingTest(fixtures.TablesTest):
         meta2 = self._useexisting_fixture()
         users = Table('users', meta2, quote=True, autoload=True,
                       keep_existing=True)
-        assert not users.quote
+        assert not users.name.quote
 
     def test_keep_existing_add_column(self):
         meta2 = self._useexisting_fixture()
@@ -1060,7 +1062,7 @@ class UseExistingTest(fixtures.TablesTest):
         users = Table('users', meta2, quote=True,
                         autoload=True,
                       keep_existing=True)
-        assert users.quote
+        assert users.name.quote
 
     def test_keep_existing_add_column_no_orig(self):
         meta2 = self._notexisting_fixture()
@@ -1080,7 +1082,7 @@ class UseExistingTest(fixtures.TablesTest):
         meta2 = self._useexisting_fixture()
         users = Table('users', meta2, quote=True,
                       keep_existing=True)
-        assert not users.quote
+        assert not users.name.quote
 
     def test_keep_existing_add_column_no_reflection(self):
         meta2 = self._useexisting_fixture()
@@ -1097,9 +1099,12 @@ class UseExistingTest(fixtures.TablesTest):
 
     def test_extend_existing_quote(self):
         meta2 = self._useexisting_fixture()
-        users = Table('users', meta2, quote=True, autoload=True,
-                      extend_existing=True)
-        assert users.quote
+        assert_raises_message(
+            tsa.exc.ArgumentError,
+            "Can't redefine 'quote' or 'quote_schema' arguments",
+            Table, 'users', meta2, quote=True, autoload=True,
+                      extend_existing=True
+        )
 
     def test_extend_existing_add_column(self):
         meta2 = self._useexisting_fixture()
@@ -1120,7 +1125,7 @@ class UseExistingTest(fixtures.TablesTest):
         users = Table('users', meta2, quote=True,
                         autoload=True,
                       extend_existing=True)
-        assert users.quote
+        assert users.name.quote
 
     def test_extend_existing_add_column_no_orig(self):
         meta2 = self._notexisting_fixture()
@@ -1138,9 +1143,12 @@ class UseExistingTest(fixtures.TablesTest):
 
     def test_extend_existing_quote_no_reflection(self):
         meta2 = self._useexisting_fixture()
-        users = Table('users', meta2, quote=True,
-                      extend_existing=True)
-        assert users.quote
+        assert_raises_message(
+            tsa.exc.ArgumentError,
+            "Can't redefine 'quote' or 'quote_schema' arguments",
+            Table, 'users', meta2, quote=True,
+                      extend_existing=True
+        )
 
     def test_extend_existing_add_column_no_reflection(self):
         meta2 = self._useexisting_fixture()
index c92f1ac8011d6a7052158b1550cfa15f392f7ed7..db1e0b8a5330800cea046d82c3a00e0e2491e5bd 100644 (file)
@@ -1,9 +1,10 @@
 from sqlalchemy import *
 from sqlalchemy import sql, schema
 from sqlalchemy.sql import compiler
-from sqlalchemy.testing import fixtures, AssertsCompiledSQL
+from sqlalchemy.testing import fixtures, AssertsCompiledSQL, eq_
 from sqlalchemy import testing
-
+from sqlalchemy.sql.elements import quoted_name, _truncated_label, _anonymous_label
+from sqlalchemy.testing.util import picklers
 
 class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):
     __dialect__ = 'default'
@@ -61,6 +62,49 @@ class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):
 
         assert 'MixedCase' in t2.c
 
+    @testing.provide_metadata
+    def test_has_table_case_sensitive(self):
+        preparer = testing.db.dialect.identifier_preparer
+        if testing.db.dialect.requires_name_normalize:
+            testing.db.execute("CREATE TABLE TAB1 (id INTEGER)")
+        else:
+            testing.db.execute("CREATE TABLE tab1 (id INTEGER)")
+        testing.db.execute('CREATE TABLE %s (id INTEGER)' %
+                    preparer.quote_identifier("tab2"))
+        testing.db.execute('CREATE TABLE %s (id INTEGER)' %
+                    preparer.quote_identifier("TAB3"))
+        testing.db.execute('CREATE TABLE %s (id INTEGER)' %
+                    preparer.quote_identifier("TAB4"))
+
+        t1 = Table('tab1', self.metadata,
+                        Column('id', Integer, primary_key=True),
+                        )
+        t2 = Table('tab2', self.metadata,
+                        Column('id', Integer, primary_key=True),
+                         quote=True
+                         )
+        t3 = Table('TAB3', self.metadata,
+                        Column('id', Integer, primary_key=True),
+                        )
+        t4 = Table('TAB4', self.metadata,
+                        Column('id', Integer, primary_key=True),
+                        quote=True)
+
+        insp = inspect(testing.db)
+        assert testing.db.has_table(t1.name)
+        eq_([c['name'] for c in insp.get_columns(t1.name)], ['id'])
+
+        assert testing.db.has_table(t2.name)
+        eq_([c['name'] for c in insp.get_columns(t2.name)], ['id'])
+
+        assert testing.db.has_table(t3.name)
+        eq_([c['name'] for c in insp.get_columns(t3.name)], ['id'])
+
+        assert testing.db.has_table(t4.name)
+        eq_([c['name'] for c in insp.get_columns(t4.name)], ['id'])
+
+
+
     def test_basic(self):
         table1.insert().execute(
             {'lowercase': 1, 'UPPERCASE': 2, 'MixedCase': 3, 'a123': 4},
@@ -299,7 +343,7 @@ class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):
             'FROM create.foreign'
         )
 
-    def test_subquery(self):
+    def test_subquery_one(self):
         # Lower case names, should not quote
         metadata = MetaData()
         t1 = Table('t1', metadata,
@@ -318,6 +362,7 @@ class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):
             'WHERE anon.col1 = :col1_1'
         )
 
+    def test_subquery_two(self):
         # Lower case names, quotes on, should quote
         metadata = MetaData()
         t1 = Table('t1', metadata,
@@ -336,6 +381,7 @@ class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):
             'WHERE anon."col1" = :col1_1'
         )
 
+    def test_subquery_three(self):
         # Not lower case names, should quote
         metadata = MetaData()
         t1 = Table('T1', metadata,
@@ -355,6 +401,8 @@ class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):
                 '"Anon"."Col1" = :Col1_1'
         )
 
+    def test_subquery_four(self):
+
         # Not lower case names, quotes off, should not quote
         metadata = MetaData()
         t1 = Table('T1', metadata,
@@ -513,7 +561,7 @@ class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):
             ') AS "Alias1"'
         )
 
-    def test_apply_labels(self):
+    def test_apply_labels_should_quote(self):
         # Not lower case names, should quote
         metadata = MetaData()
         t1 = Table('T1', metadata,
@@ -527,6 +575,7 @@ class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):
                 '"Foo"."T1"'
         )
 
+    def test_apply_labels_shouldnt_quote(self):
         # Not lower case names, quotes off
         metadata = MetaData()
         t1 = Table('T1', metadata,
@@ -619,3 +668,95 @@ class PreparerTest(fixtures.TestBase):
         a_eq(unformat('`foo`.bar'), ['foo', 'bar'])
         a_eq(unformat('`foo`.`b``a``r`.`baz`'), ['foo', 'b`a`r', 'baz'])
 
+class QuotedIdentTest(fixtures.TestBase):
+    def test_concat_quotetrue(self):
+        q1 = quoted_name("x", True)
+        self._assert_not_quoted("y" + q1)
+
+    def test_concat_quotefalse(self):
+        q1 = quoted_name("x", False)
+        self._assert_not_quoted("y" + q1)
+
+    def test_concat_quotenone(self):
+        q1 = quoted_name("x", None)
+        self._assert_not_quoted("y" + q1)
+
+    def test_rconcat_quotetrue(self):
+        q1 = quoted_name("x", True)
+        self._assert_not_quoted("y" + q1)
+
+    def test_rconcat_quotefalse(self):
+        q1 = quoted_name("x", False)
+        self._assert_not_quoted("y" + q1)
+
+    def test_rconcat_quotenone(self):
+        q1 = quoted_name("x", None)
+        self._assert_not_quoted("y" + q1)
+
+    def test_concat_anon(self):
+        q1 = _anonymous_label(quoted_name("x", True))
+        assert isinstance(q1, _anonymous_label)
+        value = q1 + "y"
+        assert isinstance(value, _anonymous_label)
+        self._assert_quoted(value, True)
+
+    def test_rconcat_anon(self):
+        q1 = _anonymous_label(quoted_name("x", True))
+        assert isinstance(q1, _anonymous_label)
+        value = "y" + q1
+        assert isinstance(value, _anonymous_label)
+        self._assert_quoted(value, True)
+
+    def test_coerce_quoted_switch(self):
+        q1 = quoted_name("x", False)
+        q2 = quoted_name(q1, True)
+        eq_(q2.quote, True)
+
+    def test_coerce_quoted_none(self):
+        q1 = quoted_name("x", False)
+        q2 = quoted_name(q1, None)
+        eq_(q2.quote, False)
+
+    def test_coerce_quoted_retain(self):
+        q1 = quoted_name("x", False)
+        q2 = quoted_name(q1, False)
+        eq_(q2.quote, False)
+
+    def test_coerce_none(self):
+        q1 = quoted_name(None, False)
+        eq_(q1, None)
+
+    def test_apply_map_quoted(self):
+        q1 = _anonymous_label(quoted_name("x%s", True))
+        q2 = q1.apply_map(('bar'))
+        eq_(q2, "xbar")
+        eq_(q2.quote, True)
+
+    def test_apply_map_plain(self):
+        q1 = _anonymous_label(quoted_name("x%s", None))
+        q2 = q1.apply_map(('bar'))
+        eq_(q2, "xbar")
+        self._assert_not_quoted(q2)
+
+    def test_pickle_quote(self):
+        q1 = quoted_name("x", True)
+        for loads, dumps in picklers():
+            q2 = loads(dumps(q1))
+            eq_(str(q1), str(q2))
+            eq_(q1.quote, q2.quote)
+
+    def test_pickle_anon_label(self):
+        q1 = _anonymous_label(quoted_name("x", True))
+        for loads, dumps in picklers():
+            q2 = loads(dumps(q1))
+            assert isinstance(q2, _anonymous_label)
+            eq_(str(q1), str(q2))
+            eq_(q1.quote, q2.quote)
+
+    def _assert_quoted(self, value, quote):
+        assert isinstance(value, quoted_name)
+        eq_(value.quote, quote)
+
+    def _assert_not_quoted(self, value):
+        assert not isinstance(value, quoted_name)
+