diff options
23 files changed, 622 insertions, 192 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index be7a7fc7c..b0492e897 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -7,6 +7,28 @@ :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 diff --git a/doc/build/changelog/migration_09.rst b/doc/build/changelog/migration_09.rst index b8328ce7b..82314cce4 100644 --- a/doc/build/changelog/migration_09.rst +++ b/doc/build/changelog/migration_09.rst @@ -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 ============ diff --git a/doc/build/core/metadata.rst b/doc/build/core/metadata.rst index e4b50f63a..d6fc8c6af 100644 --- a/doc/build/core/metadata.rst +++ b/doc/build/core/metadata.rst @@ -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. diff --git a/doc/build/core/reflection.rst b/doc/build/core/reflection.rst index 17ff0b99e..2e9a2de64 100644 --- a/doc/build/core/reflection.rst +++ b/doc/build/core/reflection.rst @@ -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: - + diff --git a/doc/build/core/sqlelement.rst b/doc/build/core/sqlelement.rst index 953f48c98..0676f18d0 100644 --- a/doc/build/core/sqlelement.rst +++ b/doc/build/core/sqlelement.rst @@ -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: diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index 7621f4aab..b1b168035 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -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 diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index b36b70774..fd6a47a10 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -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): diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py index e013799db..b82d3016f 100644 --- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py +++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py @@ -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 diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 6ccf7190e..8938b3193 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -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 diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index 735113a26..9a10e829e 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -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): diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 90c7f5993..609375c39 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -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() diff --git a/lib/sqlalchemy/engine/reflection.py b/lib/sqlalchemy/engine/reflection.py index 29ede9579..340af1abb 100644 --- a/lib/sqlalchemy/engine/reflection.py +++ b/lib/sqlalchemy/engine/reflection.py @@ -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 """ diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py index 2a3176d04..85c6e730e 100644 --- a/lib/sqlalchemy/sql/base.py +++ b/lib/sqlalchemy/sql/base.py @@ -30,6 +30,7 @@ class Immutable(object): return self + def _from_objects(*elements): return itertools.chain(*[element._from_objects for element in elements]) diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 5d05cbc29..8e3cf1729 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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), ) diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 17fb40628..99dd193f3 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -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): diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index ca83236c7..b190c3874 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -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'] diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index c32de77ea..e06262c6d 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -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"]) diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 38e0d1bd3..db0ad248c 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -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, diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index b927f1b3c..2a5c2e277 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -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 diff --git a/test/ext/declarative/test_basic.py b/test/ext/declarative/test_basic.py index 2de0032dd..8917d7772 100644 --- a/test/ext/declarative/test_basic.py +++ b/test/ext/declarative/test_basic.py @@ -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')]) diff --git a/test/profiles.txt b/test/profiles.txt index 4a392d208..b3c83391b 100644 --- a/test/profiles.txt +++ b/test/profiles.txt @@ -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 diff --git a/test/sql/test_metadata.py b/test/sql/test_metadata.py index 851e9b920..00426b227 100644 --- a/test/sql/test_metadata.py +++ b/test/sql/test_metadata.py @@ -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() diff --git a/test/sql/test_quote.py b/test/sql/test_quote.py index c92f1ac80..db1e0b8a5 100644 --- a/test/sql/test_quote.py +++ b/test/sql/test_quote.py @@ -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) + |