diff options
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/dialects/mssql/base.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/mysql/base.py | 7 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/oracle/cx_oracle.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/base.py | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/base.py | 11 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/default.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/reflection.py | 49 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/base.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 99 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/elements.py | 116 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/schema.py | 127 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/sqltypes.py | 15 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/util.py | 1 |
14 files changed, 317 insertions, 127 deletions
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 |