浏览代码

Fixed #34671 -- Fixed collation introspection for views and materialized views on Oracle.

Thanks Philipp Maino for the report.
Mariusz Felisiak 1 年之前
父节点
当前提交
a6d30f5012
共有 2 个文件被更改,包括 80 次插入6 次删除
  1. 27 4
      django/db/backends/oracle/introspection.py
  2. 53 2
      tests/backends/oracle/test_introspection.py

+ 27 - 4
django/db/backends/oracle/introspection.py

@@ -110,6 +110,31 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
         Return a description of the table with the DB-API cursor.description
         interface.
         """
+        # A default collation for the given table/view/materialized view.
+        cursor.execute(
+            """
+            SELECT user_tables.default_collation
+            FROM user_tables
+            WHERE
+                user_tables.table_name = UPPER(%s) AND
+                NOT EXISTS (
+                    SELECT 1
+                    FROM user_mviews
+                    WHERE user_mviews.mview_name = user_tables.table_name
+                )
+            UNION ALL
+            SELECT user_views.default_collation
+            FROM user_views
+            WHERE user_views.view_name = UPPER(%s)
+            UNION ALL
+            SELECT user_mviews.default_collation
+            FROM user_mviews
+            WHERE user_mviews.mview_name = UPPER(%s)
+            """,
+            [table_name, table_name, table_name],
+        )
+        row = cursor.fetchone()
+        default_table_collation = row[0] if row else ""
         # user_tab_columns gives data default for columns
         cursor.execute(
             """
@@ -117,7 +142,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
                 user_tab_cols.column_name,
                 user_tab_cols.data_default,
                 CASE
-                    WHEN user_tab_cols.collation = user_tables.default_collation
+                    WHEN user_tab_cols.collation = %s
                     THEN NULL
                     ELSE user_tab_cols.collation
                 END collation,
@@ -143,15 +168,13 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
                 END as is_json,
                 user_col_comments.comments as col_comment
             FROM user_tab_cols
-            LEFT OUTER JOIN
-                user_tables ON user_tables.table_name = user_tab_cols.table_name
             LEFT OUTER JOIN
                 user_col_comments ON
                 user_col_comments.column_name = user_tab_cols.column_name AND
                 user_col_comments.table_name = user_tab_cols.table_name
             WHERE user_tab_cols.table_name = UPPER(%s)
             """,
-            [table_name],
+            [default_table_collation, table_name],
         )
         field_map = {
             column: (

+ 53 - 2
tests/backends/oracle/test_introspection.py

@@ -1,9 +1,9 @@
 import unittest
 
 from django.db import connection
-from django.test import TransactionTestCase
+from django.test import TransactionTestCase, skipUnlessDBFeature
 
-from ..models import Square
+from ..models import Person, Square
 
 
 @unittest.skipUnless(connection.vendor == "oracle", "Oracle tests")
@@ -33,3 +33,54 @@ class DatabaseSequenceTests(TransactionTestCase):
                 # Recreate model, because adding identity is impossible.
                 editor.delete_model(Square)
                 editor.create_model(Square)
+
+    @skipUnlessDBFeature("supports_collation_on_charfield")
+    def test_get_table_description_view_default_collation(self):
+        person_table = connection.introspection.identifier_converter(
+            Person._meta.db_table
+        )
+        first_name_column = connection.ops.quote_name(
+            Person._meta.get_field("first_name").column
+        )
+        person_view = connection.introspection.identifier_converter("TEST_PERSON_VIEW")
+        with connection.cursor() as cursor:
+            cursor.execute(
+                f"CREATE VIEW {person_view} "
+                f"AS SELECT {first_name_column} FROM {person_table}"
+            )
+            try:
+                columns = connection.introspection.get_table_description(
+                    cursor, person_view
+                )
+                self.assertEqual(len(columns), 1)
+                self.assertIsNone(columns[0].collation)
+            finally:
+                cursor.execute(f"DROP VIEW {person_view}")
+
+    @skipUnlessDBFeature("supports_collation_on_charfield")
+    def test_get_table_description_materialized_view_non_default_collation(self):
+        person_table = connection.introspection.identifier_converter(
+            Person._meta.db_table
+        )
+        first_name_column = connection.ops.quote_name(
+            Person._meta.get_field("first_name").column
+        )
+        person_mview = connection.introspection.identifier_converter(
+            "TEST_PERSON_MVIEW"
+        )
+        collation = connection.features.test_collations.get("ci")
+        with connection.cursor() as cursor:
+            cursor.execute(
+                f"CREATE MATERIALIZED VIEW {person_mview} "
+                f"DEFAULT COLLATION {collation} "
+                f"AS SELECT {first_name_column} FROM {person_table}"
+            )
+            try:
+                columns = connection.introspection.get_table_description(
+                    cursor, person_mview
+                )
+                self.assertEqual(len(columns), 1)
+                self.assertIsNotNone(columns[0].collation)
+                self.assertNotEqual(columns[0].collation, collation)
+            finally:
+                cursor.execute(f"DROP MATERIALIZED VIEW {person_mview}")