Repository: mathesar-foundation/mathesar

Issue:
Add remaining types as dependent objects
## Problem
Add triggers, sequences and functions (dependent directly on schemas) as possible dependent objects



Discussion:

Resolving patch:
diff --git a/db/dependents/dependents_utils.py b/db/dependents/dependents_utils.py
--- a/db/dependents/dependents_utils.py
+++ b/db/dependents/dependents_utils.py
@@ -1,5 +1,5 @@
+from sqlalchemy import MetaData, any_, column, exists, func, literal, select, text, true, union, and_, collate
 import warnings
-from sqlalchemy import MetaData, any_, column, exists, func, literal, select, text, true, union, and_
 from sqlalchemy.dialects.postgresql import array
 
 from db.utils import get_pg_catalog_table
@@ -81,6 +81,31 @@ def _get_rule_dependents(pg_identify_object, dependency_pairs):
     return dependency_pairs.where(pg_identify_object.c.type == 'rule')
 
 
+def _get_trigger_dependents(pg_depend, pg_identify_object, pg_trigger):
+    return (
+        select(
+            pg_depend,
+            # for some reason, tgname column is in C collation which collides with other columns collations
+            collate(pg_trigger.c.tgname, 'default').label('objname'),
+            pg_identify_object.c.type.label('objtype')
+        )
+        .select_from(pg_depend)
+        .join(pg_identify_object, true())
+        .join(pg_trigger, pg_trigger.c.oid == pg_depend.c.objid)
+        .where(pg_depend.c.deptype == any_(array(PG_DEPENDENT_TYPES)))
+        .where(pg_depend.c.objid >= USER_DEFINED_OBJECTS_MIN_OID)
+        .where(pg_identify_object.c.type == 'trigger')
+        .group_by(
+            pg_depend,
+            pg_trigger.c.tgname,
+            pg_identify_object.c.type)
+    )
+
+
+def _get_sequence_dependents(pg_identify_object, dependency_pairs):
+    return dependency_pairs.where(pg_identify_object.c.type == 'sequence')
+
+
 def _get_view_dependents(pg_identify_object, pg_rewrite_table, rule_dependents):
     pg_identify_object = _get_pg_identify_object_lateral_stmt(
         text(f'{PG_CLASS_CATALOGUE_NAME}::regclass::oid'), pg_rewrite_table.c.ev_class, DEFAULT_NON_COLUMN_OBJSUBID)
@@ -109,6 +134,27 @@ def _get_table_dependents(pg_identify_object, base):
     return base.where(pg_identify_object.c.type == 'table')
 
 
+def _get_function_dependents(pg_depend, pg_identify_object, pg_proc):
+    return (
+        select(
+            pg_depend,
+            # the same as with pg_trigger table
+            collate(pg_proc.c.proname, 'default').label('objname'),
+            pg_identify_object.c.type.label('objtype')
+        )
+        .select_from(pg_depend)
+        .join(pg_identify_object, true())
+        .join(pg_proc, pg_proc.c.oid == pg_depend.c.objid)
+        .where(pg_depend.c.deptype == any_(array(PG_DEPENDENT_TYPES)))
+        .where(pg_depend.c.objid >= USER_DEFINED_OBJECTS_MIN_OID)
+        .where(pg_identify_object.c.type == 'function')
+        .group_by(
+            pg_depend,
+            pg_proc.c.proname,
+            pg_identify_object.c.type)
+    )
+
+
 # stmt for getting a full list of dependents and identifying them
 def _get_dependency_pairs_stmt(pg_depend, pg_identify_object):
     result = (
@@ -138,6 +184,14 @@ def _get_pg_rewrite(engine, metadata):
     return get_pg_catalog_table("pg_rewrite", engine, metadata=metadata)
 
 
+def _get_pg_trigger(engine, metadata):
+    return get_pg_catalog_table('pg_trigger', engine, metadata=metadata)
+
+
+def _get_pg_proc(engine, metadata):
+    return get_pg_catalog_table('pg_proc', engine, metadata=metadata)
+
+
 def _get_pg_identify_object_lateral_stmt(classid, objid, objsubid):
     return (
         select(
@@ -159,6 +213,8 @@ def _get_typed_dependency_pairs_stmt(engine, exclude_types):
     pg_identify_object = _get_pg_identify_object_lateral_stmt(
         pg_depend.c.classid, pg_depend.c.objid, pg_depend.c.objsubid)
     pg_rewrite = _get_pg_rewrite(engine, metadata)
+    pg_trigger = _get_pg_trigger(engine, metadata)
+    pg_proc = _get_pg_proc(engine, metadata)
 
     type_dependents = {}
     # each statement filters the base statement extracting dependents of a specific type
@@ -180,6 +236,16 @@ def _get_typed_dependency_pairs_stmt(engine, exclude_types):
     index_dependents = _get_index_dependents(pg_identify_object, dependency_pairs).cte('index_dependents')
     type_dependents['index'] = index_dependents
 
+    trigger_dependents = _get_trigger_dependents(pg_depend, pg_identify_object, pg_trigger).cte('trigger_dependents')
+    type_dependents['trigger'] = [trigger_dependents]
+
+    sequence_dependents = _get_sequence_dependents(pg_identify_object, dependency_pairs).cte('sequence_dependents')
+    type_dependents['sequence'] = [sequence_dependents]
+
+    # only schemas' function dependents
+    function_dependents = _get_function_dependents(pg_depend, pg_identify_object, pg_proc).cte('function_dependents')
+    type_dependents['function'] = [function_dependents]
+
     dependent_selects = [
         select(dependent)
         for type, dependent in type_dependents.items()


Test patch:
diff --git a/db/tests/dependents/test_dependents.py b/db/tests/dependents/test_dependents.py
--- a/db/tests/dependents/test_dependents.py
+++ b/db/tests/dependents/test_dependents.py
@@ -28,7 +28,7 @@ def test_correct_dependents_amount_and_level(engine, library_tables_oids):
 
     publishers_dependents = _get_object_dependents(publishers_dependents_graph, library_tables_oids['Publishers'])
 
-    assert len(publishers_dependents) == 2
+    assert len(publishers_dependents) == 3
     assert all(
         [
             r['level'] == 1
@@ -75,7 +75,12 @@ def test_constrains_as_dependents(engine, library_tables_oids, library_db_tables
         'Checkouts_Item id_fkey', library_tables_oids['Checkouts'], engine
     )
 
-    assert sorted(items_dependents_oids) == sorted(items_constraint_oids + [checkouts_items_fk_oid])
+    assert all(
+        [
+            oid in items_dependents_oids
+            for oid in items_constraint_oids + [checkouts_items_fk_oid]
+        ]
+    )
 
 
 # if a table contains a foreign key referencing itself, it shouldn't be treated as a dependent
@@ -177,7 +182,7 @@ def test_views_as_dependents(engine_with_schema, library_db_tables, library_tabl
     assert publications_view_dependent['name'] == view_name
 
 
-def test_indexex_as_dependents(engine, library_db_tables, library_tables_oids):
+def test_indexes_as_dependents(engine, library_db_tables, library_tables_oids):
     index_name = 'index'
     index = Index(index_name, library_db_tables['Publishers'].c.id)
     index.create(engine)
@@ -207,3 +212,12 @@ def test_filter(engine, library_tables_oids, exclude_types):
             type not in dependents_types for type in exclude_types
         ]
     )
+
+
+def test_sequences_as_dependents(engine, library_tables_oids):
+    publishers_oid = library_tables_oids['Publishers']
+    publishers_sequence_name = '"Publishers_id_seq"'
+    publishers_dependents_graph = get_dependents_graph(publishers_oid, engine, [])
+    publishers_sequence_dependent = _get_object_dependents_by_name(publishers_dependents_graph, publishers_oid, publishers_sequence_name)[0]
+
+    assert publishers_sequence_dependent['name'] == publishers_sequence_name
diff --git a/mathesar/tests/api/test_dependents_api.py b/mathesar/tests/api/test_dependents_api.py
--- a/mathesar/tests/api/test_dependents_api.py
+++ b/mathesar/tests/api/test_dependents_api.py
@@ -1,7 +1,10 @@
 def _get_object_dependent_ids(dependents, object_id, type):
     return [
         int(d['obj']['id'])
-        for d in dependents if int(d['parent_obj']['id']) == object_id and d['parent_obj']['type'] == type
+        for d in dependents
+        if int(d['parent_obj']['id']) == object_id
+        and d['parent_obj']['type'] == type
+        and 'id' in d['obj']
     ]
 
 
@@ -16,7 +19,7 @@ def test_dependents_response_attrs(library_ma_tables, client):
     response_data = response.json()
 
     dependent_expected_attrs = ['obj', 'parent_obj']
-    assert len(response_data) == 3
+    assert len(response_data) == 4
     assert all(
         [
             all(attr in dependent for attr in dependent_expected_attrs)