From 9f894d2f265bb5fd03ab0b3aa3fd164108c99259 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 5 Nov 2008 20:50:48 +0000 Subject: - Dialects can now generate label names of adjustable length. Pass in the argument "label_length=" to create_engine() to adjust how many characters max will be present in dynamically generated column labels, i.e. "somecolumn AS somelabel". Any value less than 6 will result in a label of minimal size, consiting of an underscore and a numeric counter. The compiler uses the value of dialect.max_identifier_length as a default. [ticket:1211] - removed ANON_NAME regular expression, using string patterns now - _generated_label() unicode subclass is used to indicate generated names which are subject to truncation --- lib/sqlalchemy/engine/default.py | 5 ++- lib/sqlalchemy/orm/query.py | 3 +- lib/sqlalchemy/orm/shard.py | 4 +- lib/sqlalchemy/sql/compiler.py | 83 ++++++++++++++++++++-------------------- lib/sqlalchemy/sql/expression.py | 19 +++++---- 5 files changed, 61 insertions(+), 53 deletions(-) (limited to 'lib/sqlalchemy') diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index f99cac465..ec15313e4 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -40,7 +40,7 @@ class DefaultDialect(base.Dialect): supports_default_values = False supports_empty_insert = True - def __init__(self, convert_unicode=False, assert_unicode=False, encoding='utf-8', paramstyle=None, dbapi=None, **kwargs): + def __init__(self, convert_unicode=False, assert_unicode=False, encoding='utf-8', paramstyle=None, dbapi=None, label_length=None, **kwargs): self.convert_unicode = convert_unicode self.assert_unicode = assert_unicode self.encoding = encoding @@ -55,6 +55,9 @@ class DefaultDialect(base.Dialect): self.paramstyle = self.default_paramstyle self.positional = self.paramstyle in ('qmark', 'format', 'numeric') self.identifier_preparer = self.preparer(self) + if label_length and label_length > self.max_identifier_length: + raise exc.ArgumentError("Label length of %d is greater than this dialect's maximum identifier length of %d" % (label_length, self.max_identifier_length)) + self.label_length = label_length def create_execution_context(self, connection, **kwargs): return DefaultExecutionContext(self, connection, **kwargs) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index cad35972d..ec378d9c6 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1210,7 +1210,8 @@ class Query(object): try: params[_get_params[primary_key].key] = ident[i] except IndexError: - raise sa_exc.InvalidRequestError("Could not find enough values to formulate primary key for query.get(); primary key columns are %s" % ', '.join("'%s'" % str(c) for c in q.mapper.primary_key)) + raise sa_exc.InvalidRequestError("Could not find enough values to formulate primary key for " + "query.get(); primary key columns are %s" % ', '.join("'%s'" % c for c in q.mapper.primary_key)) q._params = params if lockmode is not None: diff --git a/lib/sqlalchemy/orm/shard.py b/lib/sqlalchemy/orm/shard.py index b4525d8fb..395d87dbf 100644 --- a/lib/sqlalchemy/orm/shard.py +++ b/lib/sqlalchemy/orm/shard.py @@ -93,7 +93,7 @@ class ShardedQuery(Query): def _execute_and_instances(self, context): if self._shard_id is not None: - result = self.session.connection(mapper=self._mapper_zero(), shard_id=self._shard_id).execute(context.statement, **self._params) + result = self.session.connection(mapper=self._mapper_zero(), shard_id=self._shard_id).execute(context.statement, self._params) try: return iter(self.instances(result, context)) finally: @@ -101,7 +101,7 @@ class ShardedQuery(Query): else: partial = [] for shard_id in self.query_chooser(self): - result = self.session.connection(mapper=self._mapper_zero(), shard_id=shard_id).execute(context.statement, **self._params) + result = self.session.connection(mapper=self._mapper_zero(), shard_id=shard_id).execute(context.statement, self._params) try: partial = partial + list(self.instances(result, context)) finally: diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 63557f24b..1c7c66f47 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -47,7 +47,6 @@ ILLEGAL_INITIAL_CHARACTERS = re.compile(r'[0-9$]') BIND_PARAMS = re.compile(r'(? self.dialect.max_identifier_length: - counter = self.generated_ids.get(ident_class, 1) - truncname = anonname[0:self.dialect.max_identifier_length - 6] + "_" + hex(counter)[2:] - self.generated_ids[ident_class] = counter + 1 + if len(anonname) > self.label_length: + counter = self.truncated_names.get(ident_class, 1) + truncname = anonname[0:max(self.label_length - 6, 0)] + "_" + hex(counter)[2:] + self.truncated_names[ident_class] = counter + 1 else: truncname = anonname - self.generated_ids[(ident_class, name)] = truncname + self.truncated_names[(ident_class, name)] = truncname return truncname - def _process_anon(self, match): - (ident, derived) = match.group(1, 2) - - key = ('anonymous', ident) - if key in self.generated_ids: - return self.generated_ids[key] - else: - anonymous_counter = self.generated_ids.get(('anon_counter', derived), 1) - newname = derived + "_" + str(anonymous_counter) - self.generated_ids[('anon_counter', derived)] = anonymous_counter + 1 - self.generated_ids[key] = newname - return newname - def _anonymize(self, name): - return ANONYMOUS_LABEL.sub(self._process_anon, name) + return name % self.anon_map + + def _process_anon(self, key): + (ident, derived) = key.split(' ') + + anonymous_counter = self.anon_map.get(derived, 1) + self.anon_map[derived] = anonymous_counter + 1 + return derived + "_" + str(anonymous_counter) def bindparam_string(self, name): if self.positional: @@ -438,7 +439,7 @@ class DefaultCompiler(engine.Compiled): def visit_alias(self, alias, asfrom=False, **kwargs): if asfrom: - return self.process(alias.original, asfrom=True, **kwargs) + " AS " + self.preparer.format_alias(alias, self._anonymize(alias.name)) + return self.process(alias.original, asfrom=True, **kwargs) + " AS " + self.preparer.format_alias(alias, alias.name % self.anon_map) else: return self.process(alias.original, **kwargs) @@ -457,7 +458,7 @@ class DefaultCompiler(engine.Compiled): not column.is_literal and \ column.table is not None and \ not isinstance(column.table, sql.Select): - return _CompileLabel(column, column.name) + return _CompileLabel(column, sql._generated_label(column.name)) elif not isinstance(column, (sql._UnaryExpression, sql._TextClause, sql._BindParamClause)) and (not hasattr(column, 'name') or isinstance(column, sql._Function)): return _CompileLabel(column, column.anon_label) else: diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 3b996d6cb..85f229ba0 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -869,6 +869,9 @@ 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.""" + def _clone(element): return element._clone() @@ -1607,7 +1610,7 @@ class ColumnElement(ClauseElement, _CompareMixin): expressions and function calls. """ - return "{ANON %d %s}" % (id(self), getattr(self, 'name', 'anon')) + return _generated_label("%%(%d %s)s" % (id(self), getattr(self, 'name', 'anon'))) class ColumnCollection(util.OrderedProperties): """An ordered dictionary that stores a list of ColumnElement @@ -1908,9 +1911,9 @@ class _BindParamClause(ColumnElement): """ if unique: - self.key = "{ANON %d %s}" % (id(self), key or 'param') + self.key = _generated_label("%%(%d %s)s" % (id(self), key or 'param')) else: - self.key = key or "{ANON %d param}" % id(self) + self.key = key or _generated_label("%%(%d param)s" % id(self)) self._orig_key = key or 'param' self.unique = unique self.value = value @@ -1927,13 +1930,13 @@ class _BindParamClause(ColumnElement): def _clone(self): c = ClauseElement._clone(self) if self.unique: - c.key = "{ANON %d %s}" % (id(c), c._orig_key or 'param') + c.key = _generated_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 = "{ANON %d %s}" % (id(self), self._orig_key or 'param') + self.key = _generated_label("%%(%d %s)s" % (id(self), self._orig_key or 'param')) def _get_from_objects(self, **modifiers): return [] @@ -2518,7 +2521,7 @@ class Alias(FromClause): if alias is None: if self.original.named_with_column: alias = getattr(self.original, 'name', None) - alias = '{ANON %d %s}' % (id(self), alias or 'anon') + alias = _generated_label('%%(%d %s)s' % (id(self), alias or 'anon')) self.name = alias @property @@ -2637,7 +2640,7 @@ class _Label(ColumnElement): def __init__(self, name, element, type_=None): while isinstance(element, _Label): element = element.element - self.name = self.key = self._label = name or "{ANON %d %s}" % (id(self), getattr(element, 'name', 'anon')) + self.name = self.key = self._label = name or _generated_label("%%(%d %s)s" % (id(self), getattr(element, 'name', 'anon'))) self._element = element self._type = type_ self.quote = element.quote @@ -2736,7 +2739,7 @@ class _ColumnClause(_Immutable, ColumnElement): _label = label + "_" + str(counter) counter += 1 label = _label - return label + return _generated_label(label) else: return self.name -- cgit v1.2.1