Skip to content

Commit e54e4a7

Browse files
fix: Clean up PostgreSQL enum types when dropping tables
When a table with enum columns is dropped, the associated enum types should also be cleaned up to avoid orphaned types in the schema. Changes: - Added get_table_enum_types_sql() to query enum types used by a table - Added drop_enum_type_ddl() to generate DROP TYPE IF EXISTS CASCADE - Updated drop_quick() to: 1. Query for enum types before dropping the table 2. Drop the table 3. Clean up enum types (best-effort, ignores errors if type is shared) The cleanup uses CASCADE to handle any remaining dependencies and ignores errors since enum types may be shared across tables. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d8b15c5 commit e54e4a7

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

src/datajoint/adapters/postgres.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,3 +1248,74 @@ def drop_enum_type_sql(self, schema: str, table: str, column: str) -> str:
12481248
"""
12491249
type_name = f"{schema}_{table}_{column}_enum"
12501250
return f"DROP TYPE IF EXISTS {self.quote_identifier(type_name)} CASCADE"
1251+
1252+
def get_table_enum_types_sql(self, schema_name: str, table_name: str) -> str:
1253+
"""
1254+
Query to get enum types used by a table's columns.
1255+
1256+
Parameters
1257+
----------
1258+
schema_name : str
1259+
Schema name.
1260+
table_name : str
1261+
Table name.
1262+
1263+
Returns
1264+
-------
1265+
str
1266+
SQL query that returns enum type names (schema-qualified).
1267+
"""
1268+
return f"""
1269+
SELECT DISTINCT
1270+
n.nspname || '.' || t.typname as enum_type
1271+
FROM pg_catalog.pg_type t
1272+
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
1273+
JOIN pg_catalog.pg_attribute a ON a.atttypid = t.oid
1274+
JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
1275+
JOIN pg_catalog.pg_namespace cn ON cn.oid = c.relnamespace
1276+
WHERE t.typtype = 'e'
1277+
AND cn.nspname = {self.quote_string(schema_name)}
1278+
AND c.relname = {self.quote_string(table_name)}
1279+
"""
1280+
1281+
def drop_enum_types_for_table(self, schema_name: str, table_name: str) -> list[str]:
1282+
"""
1283+
Generate DROP TYPE statements for all enum types used by a table.
1284+
1285+
Parameters
1286+
----------
1287+
schema_name : str
1288+
Schema name.
1289+
table_name : str
1290+
Table name.
1291+
1292+
Returns
1293+
-------
1294+
list[str]
1295+
List of DROP TYPE IF EXISTS statements.
1296+
"""
1297+
# Returns list of DDL statements - caller should execute query first
1298+
# to get actual enum types, then call this with results
1299+
return [] # Placeholder - actual implementation requires query execution
1300+
1301+
def drop_enum_type_ddl(self, enum_type_name: str) -> str:
1302+
"""
1303+
Generate DROP TYPE IF EXISTS statement for a PostgreSQL enum.
1304+
1305+
Parameters
1306+
----------
1307+
enum_type_name : str
1308+
Fully qualified enum type name (schema.typename).
1309+
1310+
Returns
1311+
-------
1312+
str
1313+
DROP TYPE IF EXISTS statement with CASCADE.
1314+
"""
1315+
# Split schema.typename and quote each part
1316+
parts = enum_type_name.split(".")
1317+
if len(parts) == 2:
1318+
qualified_name = f"{self.quote_identifier(parts[0])}.{self.quote_identifier(parts[1])}"
1319+
else:
1320+
qualified_name = self.quote_identifier(enum_type_name)
1321+
return f"DROP TYPE IF EXISTS {qualified_name} CASCADE"

src/datajoint/table.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,9 +1052,31 @@ def drop_quick(self):
10521052

10531053
delete_table_lineages(self.connection, self.database, self.table_name)
10541054

1055+
# For PostgreSQL, get enum types used by this table before dropping
1056+
# (we need to query this before the table is dropped)
1057+
enum_types_to_drop = []
1058+
adapter = self.connection.adapter
1059+
if hasattr(adapter, "get_table_enum_types_sql"):
1060+
try:
1061+
enum_query = adapter.get_table_enum_types_sql(self.database, self.table_name)
1062+
result = self.connection.query(enum_query)
1063+
enum_types_to_drop = [row[0] for row in result.fetchall()]
1064+
except Exception:
1065+
pass # Ignore errors - enum cleanup is best-effort
1066+
10551067
query = "DROP TABLE %s" % self.full_table_name
10561068
self.connection.query(query)
10571069
logger.info("Dropped table %s" % self.full_table_name)
1070+
1071+
# For PostgreSQL, clean up enum types after dropping the table
1072+
if enum_types_to_drop and hasattr(adapter, "drop_enum_type_ddl"):
1073+
for enum_type in enum_types_to_drop:
1074+
try:
1075+
drop_ddl = adapter.drop_enum_type_ddl(enum_type)
1076+
self.connection.query(drop_ddl)
1077+
logger.debug("Dropped enum type %s" % enum_type)
1078+
except Exception:
1079+
pass # Ignore errors - type may be used by other tables
10581080
else:
10591081
logger.info("Nothing to drop: table %s is not declared" % self.full_table_name)
10601082

0 commit comments

Comments
 (0)