From 189f015c2aee644d6292702df4d2d945d9aba7b9 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 4 Sep 2006 23:05:36 +0000 Subject: [PATCH] further fixes to case sensitive logic --- lib/sqlalchemy/ansisql.py | 9 +++---- lib/sqlalchemy/schema.py | 49 +++++++++++++++++++++++++++++---------- test/sql/quote.py | 29 +++++++++++++++++++++++ 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/lib/sqlalchemy/ansisql.py b/lib/sqlalchemy/ansisql.py index d053f73898..c44595f36a 100644 --- a/lib/sqlalchemy/ansisql.py +++ b/lib/sqlalchemy/ansisql.py @@ -775,14 +775,15 @@ class ANSIIdentifierPreparer(object): if getattr(obj, 'quote', False): return self._quote_identifier(ident) if self.dialect.cache_identifiers: + case_sens = getattr(obj, 'case_sensitive', None) try: - return self.__strings[ident] + return self.__strings[(ident, case_sens)] except KeyError: if self._requires_quotes(ident, getattr(obj, 'case_sensitive', ident == ident.lower())): - self.__strings[ident] = self._quote_identifier(ident) + self.__strings[(ident, case_sens)] = self._quote_identifier(ident) else: - self.__strings[ident] = ident - return self.__strings[ident] + self.__strings[(ident, case_sens)] = ident + return self.__strings[(ident, case_sens)] else: if self._requires_quotes(ident, getattr(obj, 'case_sensitive', ident == ident.lower())): return self._quote_identifier(ident) diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index 5c7ec72b6c..4b6b8d6be0 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -43,16 +43,31 @@ class SchemaItem(object): return self._derived_metadata().engine def _set_casing_strategy(self, name, kwargs, keyname='case_sensitive'): - setattr(self, '_SchemaItem__%s_setting' % keyname, kwargs.pop(keyname, None)) + """set the "case_sensitive" argument sent via keywords to the item's constructor. + + for the purposes of Table's 'schema' property, the name of the variable is + optionally configurable.""" + setattr(self, '_%s_setting' % keyname, kwargs.pop(keyname, None)) def _determine_case_sensitive(self, name, keyname='case_sensitive'): - local = getattr(self, '_SchemaItem__%s_setting' % keyname, None) + """determine the "case_sensitive" value for this item. + + for the purposes of Table's 'schema' property, the name of the variable is + optionally configurable. + + a local non-None value overrides all others. after that, the parent item + (i.e. Column for a Sequence, Table for a Column, MetaData for a Table) is + searched for a non-None setting, traversing each parent until none are found. + finally, case_sensitive is set to True if and only if the name of this item + is not all lowercase. + """ + local = getattr(self, '_%s_setting' % keyname, None) if local is not None: return local parent = self while parent is not None: parent = parent._get_parent() if parent is not None: - parentval = getattr(parent, '_SchemaItem__case_sensitive_setting', None) + parentval = getattr(parent, '_case_sensitive_setting', None) if parentval is not None: return parentval return name is not None and name.lower() != name @@ -204,10 +219,10 @@ class Table(SchemaItem, sql.TableClause): def _get_case_sensitive_schema(self): try: - return getattr(self, '_SchemaItem__case_sensitive_schema') + return getattr(self, '_case_sensitive_schema') except AttributeError: - setattr(self, '_SchemaItem__case_sensitive_schema', self._determine_case_sensitive(self.schema or '', keyname='case_sensitive_schema')) - return getattr(self, '_SchemaItem__case_sensitive_schema') + setattr(self, '_case_sensitive_schema', self._determine_case_sensitive(self.schema or '', keyname='case_sensitive_schema')) + return getattr(self, '_case_sensitive_schema') case_sensitive_schema = property(_get_case_sensitive_schema) def _set_primary_key(self, pk): @@ -396,6 +411,7 @@ class Column(SchemaItem, sql.ColumnClause): self.quote = kwargs.pop('quote', False) self._set_casing_strategy(name, kwargs) self.onupdate = kwargs.pop('onupdate', None) + self.__originating_column = self if self.index is not None and self.unique is not None: raise exceptions.ArgumentError("Column may not define both index and unique") self._foreign_key = None @@ -458,19 +474,22 @@ class Column(SchemaItem, sql.ColumnClause): self.args = None def copy(self): - """creates a copy of this Column, unitialized""" - return Column(self.name, self.type, self.default, key = self.key, primary_key = self.primary_key, nullable = self.nullable, hidden = self.hidden, case_sensitive=self.case_sensitive, quote=self.quote) + """creates a copy of this Column, unitialized. this is used in Table.tometadata.""" + return Column(self.name, self.type, self.default, key = self.key, primary_key = self.primary_key, nullable = self.nullable, hidden = self.hidden, case_sensitive=self._case_sensitive_setting, quote=self.quote) def _make_proxy(self, selectable, name = None): - """creates a copy of this Column, initialized the way this Column is""" + """create a "proxy" for this column. + + This is a copy of this Column referenced + by a different parent (such as an alias or select statement)""" if self.foreign_key is None: fk = None else: fk = self.foreign_key.copy() - c = Column(name or self.name, self.type, fk, self.default, key = name or self.key, primary_key = self.primary_key, nullable = self.nullable, hidden = self.hidden, case_sensitive=self.case_sensitive, quote=self.quote) + c = Column(name or self.name, self.type, fk, self.default, key = name or self.key, primary_key = self.primary_key, nullable = self.nullable, hidden = self.hidden, quote=self.quote) c.table = selectable c.orig_set = self.orig_set - c._parent = self + c.__originating_column = self.__originating_column if not c.hidden: selectable.columns[c.key] = c if self.primary_key: @@ -479,6 +498,12 @@ class Column(SchemaItem, sql.ColumnClause): c._init_items(fk) return c + def _case_sens(self): + """redirect the 'case_sensitive' accessor to use the ultimate parent column which created + this one.""" + return self.__originating_column._get_case_sensitive() + case_sensitive = property(_case_sens) + def accept_schema_visitor(self, visitor): """traverses the given visitor to this Column's default and foreign key object, then calls visit_column on the visitor.""" @@ -730,7 +755,7 @@ class PrimaryKeyConstraint(Constraint): self.columns.append(col) col.primary_key=True def copy(self): - return PrimaryKeyConstraint(name=self.name, *[c.name for c in self]) + return PrimaryKeyConstraint(name=self.name, *[c.key for c in self]) class UniqueConstraint(Constraint): def __init__(self, name=None, *columns): diff --git a/test/sql/quote.py b/test/sql/quote.py index 3e1e95a266..6438586f6b 100644 --- a/test/sql/quote.py +++ b/test/sql/quote.py @@ -90,6 +90,35 @@ class QuoteTest(PersistTest): """ x = table1.select(distinct=True).alias("LaLa").select().scalar() + def testlabelsnocase(self): + metadata = MetaData() + table1 = Table('SomeCase1', metadata, + Column('lowercase', Integer, primary_key=True), + Column('UPPERCASE', Integer), + Column('MixedCase', Integer)) + table2 = Table('SomeCase2', metadata, + Column('id', Integer, primary_key=True, key='d123'), + Column('col2', Integer, key='u123'), + Column('MixedCase', Integer)) + + # first test case sensitive tables migrating via tometadata + meta = BoundMetaData(testbase.db, case_sensitive=False) + lc_table1 = table1.tometadata(meta) + lc_table2 = table2.tometadata(meta) + assert lc_table1.case_sensitive is False + assert lc_table1.c.UPPERCASE.case_sensitive is False + s = lc_table1.select() + assert hasattr(s.c.UPPERCASE, "case_sensitive") + assert s.c.UPPERCASE.case_sensitive is False + + # now, the aliases etc. should be case-insensitive. PG will screw up if this doesnt work. + # also, if this test is run in the context of the other tests, we also test that the dialect properly + # caches identifiers with "case_sensitive" and "not case_sensitive" separately. + meta.create_all() + try: + x = lc_table1.select(distinct=True).alias("lala").select().scalar() + finally: + meta.drop_all() if __name__ == "__main__": testbase.main() -- 2.47.2