diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py index eab1d4b4d7..8a8465d443 100644 --- a/django/db/backends/postgresql/introspection.py +++ b/django/db/backends/postgresql/introspection.py @@ -149,65 +149,43 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): Retrieves any constraints or keys (unique, pk, fk, check, index) across one or more columns. """ constraints = {} - # Loop over the key table, collecting things as constraints - # This will get PKs, FKs, and uniques, but not CHECK + # Loop over the key table, collecting things as constraints. The column + # array must return column names in the same order in which they were + # created. + # The subquery containing generate_series can be replaced with + # "WITH ORDINALITY" when support for PostgreSQL 9.3 is dropped. cursor.execute(""" SELECT - kc.constraint_name, - kc.column_name, - c.constraint_type, - array(SELECT table_name::text || '.' || column_name::text - FROM information_schema.constraint_column_usage - WHERE constraint_name = kc.constraint_name) - FROM information_schema.key_column_usage AS kc - JOIN information_schema.table_constraints AS c ON - kc.table_schema = c.table_schema AND - kc.table_name = c.table_name AND - kc.constraint_name = c.constraint_name - WHERE - kc.table_schema = %s AND - kc.table_name = %s - ORDER BY kc.ordinal_position ASC + c.conname, + array( + SELECT attname + FROM ( + SELECT unnest(c.conkey) AS colid, + generate_series(1, array_length(c.conkey, 1)) AS arridx + ) AS cols + JOIN pg_attribute AS ca ON cols.colid = ca.attnum + WHERE ca.attrelid = c.conrelid + ORDER BY cols.arridx + ), + c.contype, + (SELECT fkc.relname || '.' || fka.attname + FROM pg_attribute AS fka + JOIN pg_class AS fkc ON fka.attrelid = fkc.oid + WHERE fka.attrelid = c.confrelid AND fka.attnum = c.confkey[1]) + FROM pg_constraint AS c + JOIN pg_class AS cl ON c.conrelid = cl.oid + JOIN pg_namespace AS ns ON cl.relnamespace = ns.oid + WHERE ns.nspname = %s AND cl.relname = %s """, ["public", table_name]) - for constraint, column, kind, used_cols in cursor.fetchall(): - # If we're the first column, make the record - if constraint not in constraints: - constraints[constraint] = { - "columns": [], - "primary_key": kind.lower() == "primary key", - "unique": kind.lower() in ["primary key", "unique"], - "foreign_key": tuple(used_cols[0].split(".", 1)) if kind.lower() == "foreign key" else None, - "check": False, - "index": False, - } - # Record the details - constraints[constraint]['columns'].append(column) - # Now get CHECK constraint columns - cursor.execute(""" - SELECT kc.constraint_name, kc.column_name - FROM information_schema.constraint_column_usage AS kc - JOIN information_schema.table_constraints AS c ON - kc.table_schema = c.table_schema AND - kc.table_name = c.table_name AND - kc.constraint_name = c.constraint_name - WHERE - c.constraint_type = 'CHECK' AND - kc.table_schema = %s AND - kc.table_name = %s - """, ["public", table_name]) - for constraint, column in cursor.fetchall(): - # If we're the first column, make the record - if constraint not in constraints: - constraints[constraint] = { - "columns": [], - "primary_key": False, - "unique": False, - "foreign_key": None, - "check": True, - "index": False, - } - # Record the details - constraints[constraint]['columns'].append(column) + for constraint, columns, kind, used_cols in cursor.fetchall(): + constraints[constraint] = { + "columns": columns, + "primary_key": kind == "p", + "unique": kind in ["p", "u"], + "foreign_key": tuple(used_cols.split(".", 1)) if kind == "f" else None, + "check": kind == "c", + "index": False, + } # Now get indexes cursor.execute(""" SELECT