has_table/drop/etc. not really checking the "quote" flag for an uppercase engine and it probably cant

Issue #2812 resolved
Mike Bayer repo owner created an issue
diff --git a/test/sql/test_quote.py b/test/sql/test_quote.py
index c92f1ac..78c1743 100644
--- a/test/sql/test_quote.py
+++ b/test/sql/test_quote.py
@@ -61,6 +61,48 @@ class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):

         assert 'MixedCase' in t2.c

+    @testing.provide_metadata
+    def test_has_table_case_sensitive(self):
+        # get it to work on oracle...
+        m = MetaData(testing.db)
+        Table("tab1", m).drop(checkfirst=True)
+        #Table("tab2", m).drop(checkfirst=True)
+        Table("tab2", m, quote=True).drop()
+        #######
+
+        if testing.db.dialect.requires_name_normalize:
+            testing.db.execute("CREATE TABLE TAB1 (id INTEGER)")
+            testing.db.execute('CREATE TABLE "tab2" (id INTEGER)')
+            testing.db.execute('CREATE TABLE "TAB3" (id INTEGER)')
+            testing.db.execute('CREATE TABLE "TAB4" (id INTEGER)')
+        else:
+            testing.db.execute("CREATE TABLE tab1 (id INTEGER)")
+            testing.db.execute('CREATE TABLE "tab2" (id INTEGER)')
+            testing.db.execute('CREATE TABLE "TAB3" (id INTEGER)')
+            testing.db.execute('CREATE TABLE "TAB4" (id INTEGER)')
+
+        t1 = Table('tab1', self.metadata,
+                        Column('id', Integer, primary_key=True),
+                        #quote=False
+                        )
+        t2 = Table('tab2', self.metadata,
+                        Column('id', Integer, primary_key=True),
+                         quote=True
+                         )
+        t3 = Table('TAB3', self.metadata,
+                        Column('id', Integer, primary_key=True),
+                        #quote=False
+                        )
+        t4 = Table('TAB4', self.metadata,
+                        Column('id', Integer, primary_key=True),
+                        quote=True)
+
+        assert testing.db.has_table(t1.name)
+        assert testing.db.has_table(t2.name)
+        assert testing.db.has_table(t3.name)
+        assert testing.db.has_table(t4.name)
+
+
     def test_basic(self):
         table1.insert().execute(
             {'lowercase': 1, 'UPPERCASE': 2, 'MixedCase': 3, 'a123': 4},

Comments (3)

  1. Mike Bayer reporter

    here's a quick proof of concept that's making me wonder, do we just create a "case sensitive" string object right as we create Table, Sequence, Column, Index, and just leave it at that? we'd need to see how identifierpreparer and others feel about that. this allows PG and Oracle to work:

    diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
    index 90c7f59..f7dc3e0 100644
    --- a/lib/sqlalchemy/engine/default.py
    +++ b/lib/sqlalchemy/engine/default.py
    @@ -27,6 +27,13 @@ AUTOCOMMIT_REGEXP = re.compile(
                 re.I | re.UNICODE)
    
    
    +class quoted_name(util.text_type):
    +    def lower(self):
    +        return self
    +
    +    def upper(self):
    +        return self
    +
     class DefaultDialect(interfaces.Dialect):
         """Default implementation of Dialect"""
    
    @@ -160,6 +167,12 @@ class DefaultDialect(interfaces.Dialect):
             self._encoder = codecs.getencoder(self.encoding)
             self._decoder = processors.to_unicode_processor_factory(self.encoding)
    
    +    def _as_quoted(self, name, quote):
    +        if quote:
    +            return quoted_name(name)
    +        else:
    +            return name
    +
         @util.memoized_property
         def _type_memos(self):
             return weakref.WeakKeyDictionary()
    diff --git a/lib/sqlalchemy/sql/ddl.py b/lib/sqlalchemy/sql/ddl.py
    index a17c8ee..4d0de05 100644
    --- a/lib/sqlalchemy/sql/ddl.py
    +++ b/lib/sqlalchemy/sql/ddl.py
    @@ -667,7 +667,9 @@ class SchemaGenerator(DDLBase):
                 self.dialect.validate_identifier(table.schema)
             return not self.checkfirst or \
                     not self.dialect.has_table(self.connection,
    -                                    table.name, schema=table.schema)
    +                                    self.dialect._as_quoted(table.name, table.quote),
    +                                    schema=self.dialect._as_quoted(table.schema, table.quote_schema)
    +                                  )
    
         def _can_create_sequence(self, sequence):
             return self.dialect.supports_sequences and \
    @@ -678,8 +680,9 @@ class SchemaGenerator(DDLBase):
                             not self.checkfirst or
                             not self.dialect.has_sequence(
                                     self.connection,
    -                                sequence.name,
    -                                schema=sequence.schema)
    +                                self.dialect._as_quoted(sequence.name, sequence.quote),
    +                                schema=self.dialect._as_quoted(sequence.schema, sequence.quote_schema)
    +                            )
                          )
                 )
    
    @@ -787,18 +790,23 @@ class SchemaDropper(DDLBase):
             self.dialect.validate_identifier(table.name)
             if table.schema:
                 self.dialect.validate_identifier(table.schema)
    -        return not self.checkfirst or self.dialect.has_table(self.connection,
    -                                            table.name, schema=table.schema)
    +        return not self.checkfirst or \
    +              self.dialect.has_table(self.connection,
    +                        self.dialect._as_quoted(table.name, table.quote),
    +                        schema=self.dialect._as_quoted(table.schema, table.quote_schema)
    +                      )
    
         def _can_drop_sequence(self, sequence):
             return self.dialect.supports_sequences and \
                 ((not self.dialect.sequences_optional or
                      not sequence.optional) and
                     (not self.checkfirst or
    -                self.dialect.has_sequence(
    -                                self.connection,
    -                                sequence.name,
    -                                schema=sequence.schema))
    +                    self.dialect.has_sequence(
    +                        self.connection,
    +                        self.dialect._as_quoted(sequence.name, sequence.quote),
    +                        schema=self.dialect._as_quoted(sequence.schema, sequence.quote_schema)
    +                    )
    +                )
                 )
    
         def visit_index(self, index):
    diff --git a/test/sql/test_quote.py b/test/sql/test_quote.py
    index c92f1ac..ae9602e 100644
    --- a/test/sql/test_quote.py
    +++ b/test/sql/test_quote.py
    @@ -61,6 +61,42 @@ class QuoteTest(fixtures.TestBase, AssertsCompiledSQL):
    
             assert 'MixedCase' in t2.c
    
    +    @testing.provide_metadata
    +    def test_has_table_case_sensitive(self):
    +        if testing.db.dialect.requires_name_normalize:
    +            testing.db.execute("CREATE TABLE TAB1 (id INTEGER)")
    +            testing.db.execute('CREATE TABLE "tab2" (id INTEGER)')
    +            testing.db.execute('CREATE TABLE "TAB3" (id INTEGER)')
    +            testing.db.execute('CREATE TABLE "TAB4" (id INTEGER)')
    +        else:
    +            testing.db.execute("CREATE TABLE tab1 (id INTEGER)")
    +            testing.db.execute('CREATE TABLE "tab2" (id INTEGER)')
    +            testing.db.execute('CREATE TABLE "TAB3" (id INTEGER)')
    +            testing.db.execute('CREATE TABLE "TAB4" (id INTEGER)')
    +
    +        t1 = Table('tab1', self.metadata,
    +                        Column('id', Integer, primary_key=True),
    +                        #quote=False
    +                        )
    +        t2 = Table('tab2', self.metadata,
    +                        Column('id', Integer, primary_key=True),
    +                         quote=True
    +                         )
    +        t3 = Table('TAB3', self.metadata,
    +                        Column('id', Integer, primary_key=True),
    +                        #quote=False
    +                        )
    +        t4 = Table('TAB4', self.metadata,
    +                        Column('id', Integer, primary_key=True),
    +                        quote=True)
    +
    +        quoted_rule = testing.db.dialect._as_quoted
    +        assert testing.db.has_table(quoted_rule(t1.name, t1.quote))
    +        assert testing.db.has_table(quoted_rule(t2.name, t2.quote))
    +        assert testing.db.has_table(quoted_rule(t3.name, t3.quote))
    +        assert testing.db.has_table(quoted_rule(t4.name, t4.quote))
    +
    +
         def test_basic(self):
             table1.insert().execute(
                 {'lowercase': 1, 'UPPERCASE': 2, 'MixedCase': 3, 'a123': 4},
    
  2. Log in to comment