diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/dialects/firebird/base.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/oracle/base.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/sqlite/base.py | 25 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/base.py | 37 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/default.py | 5 | ||||
| -rw-r--r-- | lib/sqlalchemy/schema.py | 4 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 20 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/expression.py | 74 |
8 files changed, 111 insertions, 58 deletions
diff --git a/lib/sqlalchemy/dialects/firebird/base.py b/lib/sqlalchemy/dialects/firebird/base.py index 8cf2ded2f..031c68919 100644 --- a/lib/sqlalchemy/dialects/firebird/base.py +++ b/lib/sqlalchemy/dialects/firebird/base.py @@ -215,7 +215,7 @@ class FBCompiler(sql.compiler.SQLCompiler): # Override to not use the AS keyword which FB 1.5 does not like if asfrom: alias_name = isinstance(alias.name, - expression._generated_label) and \ + expression._truncated_label) and \ self._truncated_identifier("alias", alias.name) or alias.name diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py index 88e506287..56838f803 100644 --- a/lib/sqlalchemy/dialects/oracle/base.py +++ b/lib/sqlalchemy/dialects/oracle/base.py @@ -481,7 +481,7 @@ class OracleCompiler(compiler.SQLCompiler): """Oracle doesn't like ``FROM table AS alias``. Is the AS standard SQL??""" if asfrom or ashint: - alias_name = isinstance(alias.name, expression._generated_label) and \ + alias_name = isinstance(alias.name, expression._truncated_label) and \ self._truncated_identifier("alias", alias.name) or alias.name if ashint: diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index f9520affa..10a0d882b 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -441,20 +441,6 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer): result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result return result -class SQLiteExecutionContext(default.DefaultExecutionContext): - def get_result_proxy(self): - rp = base.ResultProxy(self) - if rp._metadata: - # adjust for dotted column names. SQLite - # in the case of UNION may store col names as - # "tablename.colname" - # in cursor.description - for colname in rp._metadata.keys: - if "." in colname: - trunc_col = colname.split(".")[1] - rp._metadata._set_keymap_synonym(trunc_col, colname) - return rp - class SQLiteDialect(default.DefaultDialect): name = 'sqlite' supports_alter = False @@ -472,7 +458,6 @@ class SQLiteDialect(default.DefaultDialect): ischema_names = ischema_names colspecs = colspecs isolation_level = None - execution_ctx_cls = SQLiteExecutionContext supports_cast = True supports_default_values = True @@ -540,6 +525,16 @@ class SQLiteDialect(default.DefaultDialect): else: return None + def _translate_colname(self, colname): + # adjust for dotted column names. SQLite + # in the case of UNION may store col names as + # "tablename.colname" + # in cursor.description + if "." in colname: + return colname.split(".")[1], colname + else: + return colname, None + @reflection.cache def get_table_names(self, connection, schema=None, **kw): if schema is not None: diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index db19fe7de..3175ebabd 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -2575,6 +2575,10 @@ class ResultMetaData(object): context = parent.context dialect = context.dialect typemap = dialect.dbapi_type_map + translate_colname = dialect._translate_colname + + # high precedence key values. + primary_keymap = {} for i, rec in enumerate(metadata): colname = rec[0] @@ -2583,6 +2587,9 @@ class ResultMetaData(object): if dialect.description_encoding: colname = dialect._description_decoder(colname) + if translate_colname: + colname, untranslated = translate_colname(colname) + if context.result_map: try: name, obj, type_ = context.result_map[colname.lower()] @@ -2600,15 +2607,17 @@ class ResultMetaData(object): # indexes as keys. This is only needed for the Python version of # RowProxy (the C version uses a faster path for integer indexes). - keymap[i] = rec - - # Column names as keys - if keymap.setdefault(name.lower(), rec) is not rec: - # We do not raise an exception directly because several - # columns colliding by name is not a problem as long as the - # user does not try to access them (ie use an index directly, - # or the more precise ColumnElement) - keymap[name.lower()] = (processor, obj, None) + primary_keymap[i] = rec + + # populate primary keymap, looking for conflicts. + if primary_keymap.setdefault(name.lower(), rec) is not rec: + # place a record that doesn't have the "index" - this + # is interpreted later as an AmbiguousColumnError, + # but only when actually accessed. Columns + # colliding by name is not a problem if those names + # aren't used; integer and ColumnElement access is always + # unambiguous. + primary_keymap[name.lower()] = (processor, obj, None) if dialect.requires_name_normalize: colname = dialect.normalize_name(colname) @@ -2618,10 +2627,20 @@ class ResultMetaData(object): for o in obj: keymap[o] = rec + if translate_colname and \ + untranslated: + keymap[untranslated] = rec + + # overwrite keymap values with those of the + # high precedence keymap. + keymap.update(primary_keymap) + if parent._echo: context.engine.logger.debug( "Col %r", tuple(x[0] for x in metadata)) + @util.pending_deprecation("0.8", "sqlite dialect uses " + "_translate_colname() now") def _set_keymap_synonym(self, name, origname): """Set a synonym for the given name. diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 73bd7fd71..a1e5a5799 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -44,6 +44,7 @@ class DefaultDialect(base.Dialect): postfetch_lastrowid = True implicit_returning = False + supports_native_enum = False supports_native_boolean = False @@ -95,6 +96,10 @@ class DefaultDialect(base.Dialect): # and denormalize_name() must be provided. requires_name_normalize = False + # a hook for SQLite's translation of + # result column names + _translate_colname = None + reflection_options = () def __init__(self, convert_unicode=False, assert_unicode=False, diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index f0a929776..c183e4385 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -1028,7 +1028,7 @@ class Column(SchemaItem, expression.ColumnClause): "The 'index' keyword argument on Column is boolean only. " "To create indexes with a specific name, create an " "explicit Index object external to the Table.") - Index(expression._generated_label('ix_%s' % self._label), self, unique=self.unique) + Index(expression._truncated_label('ix_%s' % self._label), self, unique=self.unique) elif self.unique: if isinstance(self.unique, basestring): raise exc.ArgumentError( @@ -1093,7 +1093,7 @@ class Column(SchemaItem, expression.ColumnClause): "been assigned.") try: c = self._constructor( - name or self.name, + expression._as_truncated(name or self.name), self.type, key = name or self.key, primary_key = self.primary_key, diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index b0a55b886..598e87932 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -354,7 +354,7 @@ class SQLCompiler(engine.Compiled): # or ORDER BY clause of a select. dialect-specific compilers # can modify this behavior. if within_columns_clause and not within_label_clause: - if isinstance(label.name, sql._generated_label): + if isinstance(label.name, sql._truncated_label): labelname = self._truncated_identifier("colident", label.name) else: labelname = label.name @@ -376,17 +376,17 @@ class SQLCompiler(engine.Compiled): **kw) def visit_column(self, column, result_map=None, **kwargs): - name = column.name + name = orig_name = column.name if name is None: raise exc.CompileError("Cannot compile Column object until " "it's 'name' is assigned.") is_literal = column.is_literal - if not is_literal and isinstance(name, sql._generated_label): + if not is_literal and isinstance(name, sql._truncated_label): name = self._truncated_identifier("colident", name) if result_map is not None: - result_map[name.lower()] = (name, (column, ), column.type) + result_map[name.lower()] = (orig_name, (column, name), column.type) if is_literal: name = self.escape_literal_column(name) @@ -404,7 +404,7 @@ class SQLCompiler(engine.Compiled): else: schema_prefix = '' tablename = table.name - if isinstance(tablename, sql._generated_label): + if isinstance(tablename, sql._truncated_label): tablename = self._truncated_identifier("alias", tablename) return schema_prefix + \ @@ -703,7 +703,7 @@ class SQLCompiler(engine.Compiled): return self.bind_names[bindparam] bind_name = bindparam.key - if isinstance(bind_name, sql._generated_label): + if isinstance(bind_name, sql._truncated_label): bind_name = self._truncated_identifier("bindparam", bind_name) # add to bind_names for translation @@ -715,7 +715,7 @@ class SQLCompiler(engine.Compiled): if (ident_class, name) in self.truncated_names: return self.truncated_names[(ident_class, name)] - anonname = name % self.anon_map + anonname = name.apply_map(self.anon_map) if len(anonname) > self.label_length: counter = self.truncated_names.get(ident_class, 1) @@ -747,7 +747,7 @@ class SQLCompiler(engine.Compiled): def visit_alias(self, alias, asfrom=False, ashint=False, fromhints=None, **kwargs): if asfrom or ashint: - if isinstance(alias.name, sql._generated_label): + if isinstance(alias.name, sql._truncated_label): alias_name = self._truncated_identifier("alias", alias.name) else: alias_name = alias.name @@ -784,7 +784,7 @@ class SQLCompiler(engine.Compiled): not column.is_literal and \ column.table is not None and \ not isinstance(column.table, sql.Select): - return _CompileLabel(column, sql._generated_label(column.name)) + return _CompileLabel(column, sql._as_truncated(column.name)) elif not isinstance(column, (sql._UnaryExpression, sql._TextClause)) \ and (not hasattr(column, 'name') or \ @@ -1445,7 +1445,7 @@ class DDLCompiler(engine.Compiled): return "\nDROP TABLE " + self.preparer.format_table(drop.element) def _index_identifier(self, ident): - if isinstance(ident, sql._generated_label): + if isinstance(ident, sql._truncated_label): max = self.dialect.max_index_name_length or \ self.dialect.max_identifier_length if len(ident) > max: diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 859ee0437..939456b9a 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -1283,14 +1283,48 @@ func = _FunctionGenerator() # TODO: use UnaryExpression for this instead ? modifier = _FunctionGenerator(group=False) -class _generated_label(unicode): - """A unicode subclass used to identify dynamically generated names.""" +class _truncated_label(unicode): + """A unicode subclass used to identify symbolic " + "names that may require truncation.""" -def _escape_for_generated(x): - if isinstance(x, _generated_label): - return x + def apply_map(self, map_): + return self + +# for backwards compatibility in case +# someone is re-implementing the +# _truncated_identifier() sequence in a custom +# compiler +_generated_label = _truncated_label + +class _anonymous_label(_truncated_label): + """A unicode subclass used to identify anonymously + generated names.""" + + def __add__(self, other): + return _anonymous_label( + unicode(self) + + unicode(other)) + + def __radd__(self, other): + return _anonymous_label( + unicode(other) + + unicode(self)) + + def apply_map(self, map_): + return self % map_ + +def _as_truncated(value): + """coerce the given value to :class:`._truncated_label`. + + Existing :class:`._truncated_label` and + :class:`._anonymous_label` objects are passed + unchanged. + """ + + if isinstance(value, _truncated_label): + return value else: - return x.replace('%', '%%') + return _truncated_label(value) def _string_or_unprintable(element): if isinstance(element, basestring): @@ -2117,7 +2151,9 @@ class ColumnElement(ClauseElement, _CompareMixin): else: key = name - co = ColumnClause(name, selectable, type_=getattr(self, + co = ColumnClause(_as_truncated(name), + selectable, + type_=getattr(self, 'type', None)) co.proxies = [self] selectable._columns[key] = co @@ -2165,7 +2201,7 @@ class ColumnElement(ClauseElement, _CompareMixin): expressions and function calls. """ - return _generated_label('%%(%d %s)s' % (id(self), getattr(self, + return _anonymous_label('%%(%d %s)s' % (id(self), getattr(self, 'name', 'anon'))) class ColumnCollection(util.OrderedProperties): @@ -2588,10 +2624,10 @@ class _BindParamClause(ColumnElement): """ if unique: - self.key = _generated_label('%%(%d %s)s' % (id(self), key + self.key = _anonymous_label('%%(%d %s)s' % (id(self), key or 'param')) else: - self.key = key or _generated_label('%%(%d param)s' + self.key = key or _anonymous_label('%%(%d param)s' % id(self)) # identifiying key that won't change across @@ -2639,14 +2675,14 @@ class _BindParamClause(ColumnElement): def _clone(self): c = ClauseElement._clone(self) if self.unique: - c.key = _generated_label('%%(%d %s)s' % (id(c), c._orig_key + c.key = _anonymous_label('%%(%d %s)s' % (id(c), c._orig_key or 'param')) return c def _convert_to_unique(self): if not self.unique: self.unique = True - self.key = _generated_label('%%(%d %s)s' % (id(self), + self.key = _anonymous_label('%%(%d %s)s' % (id(self), self._orig_key or 'param')) def compare(self, other, **kw): @@ -3615,7 +3651,7 @@ class Alias(FromClause): if name is None: if self.original.named_with_column: name = getattr(self.original, 'name', None) - name = _generated_label('%%(%d %s)s' % (id(self), name + name = _anonymous_label('%%(%d %s)s' % (id(self), name or 'anon')) self.name = name @@ -3816,7 +3852,7 @@ class _Label(ColumnElement): while isinstance(element, _Label): element = element.element self.name = self.key = self._label = name \ - or _generated_label('%%(%d %s)s' % (id(self), + or _anonymous_label('%%(%d %s)s' % (id(self), getattr(element, 'name', 'anon'))) self._element = element self._type = type_ @@ -3973,11 +4009,9 @@ class ColumnClause(_Immutable, ColumnElement): elif t is not None and t.named_with_column: if getattr(t, 'schema', None): label = t.schema.replace('.', '_') + "_" + \ - _escape_for_generated(t.name) + "_" + \ - _escape_for_generated(self.name) + t.name + "_" + self.name else: - label = _escape_for_generated(t.name) + "_" + \ - _escape_for_generated(self.name) + label = t.name + "_" + self.name # ensure the label name doesn't conflict with that # of an existing column @@ -3989,7 +4023,7 @@ class ColumnClause(_Immutable, ColumnElement): counter += 1 label = _label - return _generated_label(label) + return _as_truncated(label) else: return self.name @@ -4018,7 +4052,7 @@ class ColumnClause(_Immutable, ColumnElement): # otherwise its considered to be a label is_literal = self.is_literal and (name is None or name == self.name) c = self._constructor( - name or self.name, + _as_truncated(name or self.name), selectable=selectable, type_=self.type, is_literal=is_literal |
