summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/schema.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-08-27 20:43:22 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2013-08-27 20:43:22 -0400
commit031ef0807838842a827135dbace760da7aec215e (patch)
treea677555dd6f39e64da0880035a378ed4323c8e82 /lib/sqlalchemy/sql/schema.py
parent99732dd29bd69a4a3808bfaa86c8e378d7a5e28b (diff)
downloadsqlalchemy-031ef0807838842a827135dbace760da7aec215e.tar.gz
- 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]
Diffstat (limited to 'lib/sqlalchemy/sql/schema.py')
-rw-r--r--lib/sqlalchemy/sql/schema.py127
1 files changed, 86 insertions, 41 deletions
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']