Skip to content
Permalink
Browse files
- 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.
[ticket:2812]
  • Loading branch information
zzzeek committed Aug 28, 2013
1 parent 99732dd commit 031ef0807838842a827135dbace760da7aec215e
Show file tree
Hide file tree
Showing 23 changed files with 622 additions and 192 deletions.
@@ -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
@@ -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
============

@@ -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.

@@ -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:


@@ -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:

@@ -1012,7 +1012,7 @@ def visit_create_index(self, create, include_schema=False):
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 @@ def __init__(self, dialect):
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
@@ -1451,7 +1451,7 @@ def create_table_constraints(self, table):
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 @@ def visit_create_index(self, create):

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 @@ def visit_primary_key_constraint(self, constraint):
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):
@@ -362,7 +362,8 @@ def get_dbapi_type(self, dbapi):


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
@@ -1094,7 +1094,7 @@ def visit_create_index(self, create):

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 @@ def visit_exclude_constraint(self, constraint):
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 @@ def format_type(self, type_, use_schema=True):
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


@@ -1672,6 +1672,17 @@ def table_names(self, schema=None, connection=None):
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):
@@ -27,6 +27,7 @@
re.I | re.UNICODE)



class DefaultDialect(interfaces.Dialect):
"""Default implementation of Dialect"""

@@ -160,6 +161,7 @@ def __init__(self, convert_unicode=False,
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()
@@ -169,7 +169,7 @@ def get_table_names(self, schema=None, order_by=None):
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 @@ def get_table_options(self, table_name, schema=None, **kw):
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 @@ def get_view_names(self, schema=None):
"""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 @@ def get_view_definition(self, view_name, schema=None):
"""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 @@ def get_columns(self, table_name, schema=None, **kw):
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 @@ def get_pk_constraint(self, table_name, schema=None, **kw):
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 @@ def get_foreign_keys(self, table_name, schema=None, **kw):
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 @@ def get_indexes(self, table_name, schema=None, **kw):
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 @@ def get_unique_constraints(self, table_name, schema=None, **kw):
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
"""
@@ -30,6 +30,7 @@ def _clone(self):
return self



def _from_objects(*elements):
return itertools.chain(*[element._from_objects for element in elements])

0 comments on commit 031ef08

Please sign in to comment.