summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/dialects/mssql/base.py4
-rw-r--r--lib/sqlalchemy/dialects/mysql/base.py7
-rw-r--r--lib/sqlalchemy/dialects/oracle/cx_oracle.py3
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py8
-rw-r--r--lib/sqlalchemy/engine/base.py11
-rw-r--r--lib/sqlalchemy/engine/default.py2
-rw-r--r--lib/sqlalchemy/engine/reflection.py49
-rw-r--r--lib/sqlalchemy/sql/base.py1
-rw-r--r--lib/sqlalchemy/sql/compiler.py99
-rw-r--r--lib/sqlalchemy/sql/elements.py116
-rw-r--r--lib/sqlalchemy/sql/schema.py127
-rw-r--r--lib/sqlalchemy/sql/selectable.py1
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py15
-rw-r--r--lib/sqlalchemy/sql/util.py1
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