diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-08-27 20:43:22 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-08-27 20:43:22 -0400 |
commit | 031ef0807838842a827135dbace760da7aec215e (patch) | |
tree | a677555dd6f39e64da0880035a378ed4323c8e82 /lib/sqlalchemy/sql/schema.py | |
parent | 99732dd29bd69a4a3808bfaa86c8e378d7a5e28b (diff) | |
download | sqlalchemy-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.py | 127 |
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'] |