summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnv3sh <anveshgreat11@gmail.com>2022-05-12 01:00:13 +0530
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-06-01 08:40:44 +0200
commit295249c901e13ec1703ada5c414cd97aba72f3e9 (patch)
treea2997378671133fdb93ca12aa7f855e42f23f71d
parentebf25555bbed3e9112d4b726575d60b242daf48a (diff)
Fixed #32234 -- Made inspectdb inform about composite primary keys.
-rw-r--r--django/core/management/commands/inspectdb.py13
-rw-r--r--django/db/backends/base/features.py4
-rw-r--r--django/db/backends/base/introspection.py7
-rw-r--r--django/db/backends/mysql/features.py7
-rw-r--r--django/db/backends/oracle/features.py7
-rw-r--r--django/db/backends/oracle/introspection.py10
-rw-r--r--django/db/backends/postgresql/features.py7
-rw-r--r--django/db/backends/sqlite3/features.py7
-rw-r--r--django/db/backends/sqlite3/introspection.py14
-rw-r--r--tests/inspectdb/tests.py29
10 files changed, 88 insertions, 17 deletions
diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py
index 43674dccf3..f33bcbdccf 100644
--- a/django/core/management/commands/inspectdb.py
+++ b/django/core/management/commands/inspectdb.py
@@ -106,11 +106,14 @@ class Command(BaseCommand):
)
except NotImplementedError:
constraints = {}
- primary_key_column = (
- connection.introspection.get_primary_key_column(
+ primary_key_columns = (
+ connection.introspection.get_primary_key_columns(
cursor, table_name
)
)
+ primary_key_column = (
+ primary_key_columns[0] if primary_key_columns else None
+ )
unique_columns = [
c["columns"][0]
for c in constraints.values()
@@ -150,6 +153,12 @@ class Command(BaseCommand):
# Add primary_key and unique, if necessary.
if column_name == primary_key_column:
extra_params["primary_key"] = True
+ if len(primary_key_columns) > 1:
+ comment_notes.append(
+ "The composite primary key (%s) found, that is not "
+ "supported. The first column is selected."
+ % ", ".join(primary_key_columns)
+ )
elif column_name in unique_columns:
extra_params["unique"] = True
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index 68cad9fef2..c54d30cf73 100644
--- a/django/db/backends/base/features.py
+++ b/django/db/backends/base/features.py
@@ -260,6 +260,10 @@ class BaseDatabaseFeatures:
create_test_procedure_without_params_sql = None
create_test_procedure_with_int_param_sql = None
+ # SQL to create a table with a composite primary key for use by the Django
+ # test suite.
+ create_test_table_with_composite_primary_key = None
+
# Does the backend support keyword parameters for cursor.callproc()?
supports_callproc_kwargs = False
diff --git a/django/db/backends/base/introspection.py b/django/db/backends/base/introspection.py
index 8bf456225e..5e4acb3ff9 100644
--- a/django/db/backends/base/introspection.py
+++ b/django/db/backends/base/introspection.py
@@ -177,9 +177,14 @@ class BaseDatabaseIntrospection:
"""
Return the name of the primary key column for the given table.
"""
+ columns = self.get_primary_key_columns(cursor, table_name)
+ return columns[0] if columns else None
+
+ def get_primary_key_columns(self, cursor, table_name):
+ """Return a list of primary key columns for the given table."""
for constraint in self.get_constraints(cursor, table_name).values():
if constraint["primary_key"]:
- return constraint["columns"][0]
+ return constraint["columns"]
return None
def get_constraints(self, cursor, table_name):
diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py
index 5387dffb9c..3ea3deeae3 100644
--- a/django/db/backends/mysql/features.py
+++ b/django/db/backends/mysql/features.py
@@ -39,6 +39,13 @@ class DatabaseFeatures(BaseDatabaseFeatures):
SET V_I = P_I;
END;
"""
+ create_test_table_with_composite_primary_key = """
+ CREATE TABLE test_table_composite_pk (
+ column_1 INTEGER NOT NULL,
+ column_2 INTEGER NOT NULL,
+ PRIMARY KEY(column_1, column_2)
+ )
+ """
# Neither MySQL nor MariaDB support partial indexes.
supports_partial_indexes = False
# COLLATE must be wrapped in parentheses because MySQL treats COLLATE as an
diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py
index defd0a0ff8..289f786f5e 100644
--- a/django/db/backends/oracle/features.py
+++ b/django/db/backends/oracle/features.py
@@ -54,6 +54,13 @@ class DatabaseFeatures(BaseDatabaseFeatures):
V_I := P_I;
END;
"""
+ create_test_table_with_composite_primary_key = """
+ CREATE TABLE test_table_composite_pk (
+ column_1 NUMBER(11) NOT NULL,
+ column_2 NUMBER(11) NOT NULL,
+ PRIMARY KEY (column_1, column_2)
+ )
+ """
supports_callproc_kwargs = True
supports_over_clause = True
supports_frame_range_fixed_distance = True
diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py
index 0fba9a21ed..ce743b291d 100644
--- a/django/db/backends/oracle/introspection.py
+++ b/django/db/backends/oracle/introspection.py
@@ -248,7 +248,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
for field_name, rel_table_name, rel_field_name in cursor.fetchall()
}
- def get_primary_key_column(self, cursor, table_name):
+ def get_primary_key_columns(self, cursor, table_name):
cursor.execute(
"""
SELECT
@@ -259,13 +259,13 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
WHERE
user_constraints.constraint_name = cols.constraint_name AND
user_constraints.constraint_type = 'P' AND
- user_constraints.table_name = UPPER(%s) AND
- cols.position = 1
+ user_constraints.table_name = UPPER(%s)
+ ORDER BY
+ cols.position
""",
[table_name],
)
- row = cursor.fetchone()
- return self.identifier_converter(row[0]) if row else None
+ return [self.identifier_converter(row[0]) for row in cursor.fetchall()]
def get_constraints(self, cursor, table_name):
"""
diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py
index cbf499d785..0eed8c8d63 100644
--- a/django/db/backends/postgresql/features.py
+++ b/django/db/backends/postgresql/features.py
@@ -49,6 +49,13 @@ class DatabaseFeatures(BaseDatabaseFeatures):
V_I := P_I;
END;
$$ LANGUAGE plpgsql;"""
+ create_test_table_with_composite_primary_key = """
+ CREATE TABLE test_table_composite_pk (
+ column_1 INTEGER NOT NULL,
+ column_2 INTEGER NOT NULL,
+ PRIMARY KEY(column_1, column_2)
+ )
+ """
requires_casted_case_in_updates = True
supports_over_clause = True
only_supports_unbounded_with_preceding_and_following = True
diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py
index 2886ecc3be..6e10e61572 100644
--- a/django/db/backends/sqlite3/features.py
+++ b/django/db/backends/sqlite3/features.py
@@ -55,6 +55,13 @@ class DatabaseFeatures(BaseDatabaseFeatures):
# Date/DateTime fields and timedeltas.
"expressions.tests.FTimeDeltaTests.test_mixed_comparisons1",
}
+ create_test_table_with_composite_primary_key = """
+ CREATE TABLE test_table_composite_pk (
+ column_1 INTEGER NOT NULL,
+ column_2 INTEGER NOT NULL,
+ PRIMARY KEY(column_1, column_2)
+ )
+ """
@cached_property
def django_test_skips(self):
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
index a74153757b..4805305aa5 100644
--- a/django/db/backends/sqlite3/introspection.py
+++ b/django/db/backends/sqlite3/introspection.py
@@ -156,15 +156,11 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
) in cursor.fetchall()
}
- def get_primary_key_column(self, cursor, table_name):
- """Return the column name of the primary key for the given table."""
+ def get_primary_key_columns(self, cursor, table_name):
cursor.execute(
"PRAGMA table_info(%s)" % self.connection.ops.quote_name(table_name)
)
- for _, name, *_, pk in cursor.fetchall():
- if pk:
- return name
- return None
+ return [name for _, name, *_, pk in cursor.fetchall() if pk]
def _parse_column_or_constraint_definition(self, tokens, columns):
token = None
@@ -372,14 +368,14 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
if orders is not None:
constraints[index]["orders"] = orders
# Get the PK
- pk_column = self.get_primary_key_column(cursor, table_name)
- if pk_column:
+ pk_columns = self.get_primary_key_columns(cursor, table_name)
+ if pk_columns:
# SQLite doesn't actually give a name to the PK constraint,
# so we invent one. This is fine, as the SQLite backend never
# deletes PK constraints by name, as you can't delete constraints
# in SQLite; we remake the table with a new PK instead.
constraints["__primary__"] = {
- "columns": [pk_column],
+ "columns": pk_columns,
"primary_key": True,
"unique": False, # It's not actually a unique constraint.
"foreign_key": None,
diff --git a/tests/inspectdb/tests.py b/tests/inspectdb/tests.py
index 39d5b6cbe5..76c5e2b940 100644
--- a/tests/inspectdb/tests.py
+++ b/tests/inspectdb/tests.py
@@ -585,3 +585,32 @@ class InspectDBTransactionalTests(TransactionTestCase):
)
cursor.execute("DROP SERVER IF EXISTS inspectdb_server")
cursor.execute("DROP EXTENSION IF EXISTS file_fdw")
+
+ @skipUnlessDBFeature("create_test_table_with_composite_primary_key")
+ def test_composite_primary_key(self):
+ table_name = "test_table_composite_pk"
+ with connection.cursor() as cursor:
+ cursor.execute(
+ connection.features.create_test_table_with_composite_primary_key
+ )
+ out = StringIO()
+ if connection.vendor == "sqlite":
+ field_type = connection.features.introspected_field_types["AutoField"]
+ else:
+ field_type = connection.features.introspected_field_types["IntegerField"]
+ try:
+ call_command("inspectdb", table_name, stdout=out)
+ output = out.getvalue()
+ self.assertIn(
+ f"column_1 = models.{field_type}(primary_key=True) # The composite "
+ f"primary key (column_1, column_2) found, that is not supported. The "
+ f"first column is selected.",
+ output,
+ )
+ self.assertIn(
+ "column_2 = models.IntegerField()",
+ output,
+ )
+ finally:
+ with connection.cursor() as cursor:
+ cursor.execute("DROP TABLE %s" % table_name)