From c7a6680a1748bfa28102cc05ce49d83042ed0ae5 Mon Sep 17 00:00:00 2001 From: Pierre-Marie Petit Date: Thu, 13 Nov 2025 15:48:38 +0100 Subject: [PATCH 1/3] exclude schema for t rules --- dblinter/rules/T001/TableWithoutPrimaryKey.py | 5 +++-- dblinter/rules/T002/TableWithoutIndex.py | 6 ++++-- dblinter/rules/T003/TableWithRedundantIndex.py | 3 ++- dblinter/rules/T004/TableWithFkNotIndexed.py | 7 +++++-- dblinter/rules/T005/TableWithPotentialMissingIdx.py | 4 ++-- dblinter/rules/T008/TableWithFkMismatch.py | 2 ++ dblinter/rules/T009/TableWithRoleNotGranted.py | 3 ++- dblinter/rules/T010/ReservedKeyWord.py | 2 ++ dblinter/rules/T011/TableWithUppercase.py | 3 ++- 9 files changed, 24 insertions(+), 11 deletions(-) diff --git a/dblinter/rules/T001/TableWithoutPrimaryKey.py b/dblinter/rules/T001/TableWithoutPrimaryKey.py index fc85b4d..a591642 100644 --- a/dblinter/rules/T001/TableWithoutPrimaryKey.py +++ b/dblinter/rules/T001/TableWithoutPrimaryKey.py @@ -1,6 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection +from dblinter.function_library import EXCLUDED_SCHEMAS_STR LOGGER = logging.getLogger("dblinter") @@ -15,10 +16,10 @@ def table_without_primary_key( NB_PK = """SELECT count(*) FROM pg_catalog.pg_class pc JOIN pg_catalog.pg_namespace pn ON pc.relnamespace = pn.oid JOIN pg_catalog.pg_index pi ON pi.indrelid = pc.oid - WHERE pi.indisprimary=true AND nspname='{}' AND relname='{}' + WHERE pi.indisprimary=true AND nspname='{}' AND relname='{}' and nspname NOT IN ('{}') """ uri = f"{db.database}.{table[0]}.{table[1]}" - pk_count = db.query(NB_PK.format(table[0], table[1]))[0][0] + pk_count = db.query(NB_PK.format(table[0], table[1], EXCLUDED_SCHEMAS_STR))[0][0] if pk_count == 0: message_args = (db.database, table[0], table[1]) sarif_document.add_check( diff --git a/dblinter/rules/T002/TableWithoutIndex.py b/dblinter/rules/T002/TableWithoutIndex.py index eb4adb3..0775963 100644 --- a/dblinter/rules/T002/TableWithoutIndex.py +++ b/dblinter/rules/T002/TableWithoutIndex.py @@ -1,6 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection +from dblinter.function_library import EXCLUDED_SCHEMAS_STR LOGGER = logging.getLogger("dblinter") @@ -11,10 +12,11 @@ def table_without_index( LOGGER.debug( "table_without_index for %s.%s in db %s", table[0], table[1], db.database ) - NB_TABLE_WITHOUT_INDEX = """SELECT count(*) FROM pg_catalog.pg_indexes WHERE schemaname='{}' AND tablename='{}'""" + NB_TABLE_WITHOUT_INDEX = """SELECT count(*) FROM pg_catalog.pg_indexes WHERE schemaname='{}' AND tablename='{}' + AND schemaname NOT ('{}')""" uri = f"{db.database}.{table[0]}.{table[1]}" - index_count = db.query(NB_TABLE_WITHOUT_INDEX.format(table[0], table[1]))[0][0] + index_count = db.query(NB_TABLE_WITHOUT_INDEX.format(table[0], table[1], EXCLUDED_SCHEMAS_STR))[0][0] if index_count == 0: message_args = (db.database, table[0], table[1]) sarif_document.add_check( diff --git a/dblinter/rules/T003/TableWithRedundantIndex.py b/dblinter/rules/T003/TableWithRedundantIndex.py index 5f1689a..889dda3 100644 --- a/dblinter/rules/T003/TableWithRedundantIndex.py +++ b/dblinter/rules/T003/TableWithRedundantIndex.py @@ -1,6 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection +from dblinter.function_library import EXCLUDED_SCHEMAS_STR LOGGER = logging.getLogger("dblinter") @@ -17,7 +18,7 @@ def table_with_redundant_index( pg_namespace ns WHERE c.oid = i.indrelid AND ns.oid = c.relnamespace - AND ns.nspname||'.'||c.relname = '{table[0]}.{table[1]}' + AND ns.nspname||'.'||c.relname = '{table[0]}.{table[1]}' AND ns.nspname NOT IN ('{EXCLUDED_SCHEMAS_STR}') GROUP BY indrelid,indkey,ns.nspname,c.relname HAVING COUNT(*) > 1; """ diff --git a/dblinter/rules/T004/TableWithFkNotIndexed.py b/dblinter/rules/T004/TableWithFkNotIndexed.py index 3b7f95a..41b3ef4 100644 --- a/dblinter/rules/T004/TableWithFkNotIndexed.py +++ b/dblinter/rules/T004/TableWithFkNotIndexed.py @@ -1,6 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection +from dblinter.function_library import EXCLUDED_SCHEMAS_STR LOGGER = logging.getLogger("dblinter") @@ -23,7 +24,7 @@ def table_without_index_on_fk( join pg_catalog.pg_attribute ta on ta.attnum = tx.attnum and ta.attrelid = tc.conrelid inner join pg_class c on c.oid=tc.conrelid inner join pg_namespace ns on ns.oid = c.relnamespace - where ns.nspname='{}' and c.relname='{}' and not exists ( + where ns.nspname NOT IN ('{}') AND ns.nspname='{}' and c.relname='{}' and not exists ( select 1 from pg_catalog.pg_index i where i.indrelid = tc.conrelid and @@ -33,7 +34,9 @@ def table_without_index_on_fk( tc.conrelid, tc.conname, tc.confrelid""" - index_count = db.query(NB_TABLE_WITHOUT_INDEX_ON_FK.format(table[0], table[1])) + index_count = db.query( + NB_TABLE_WITHOUT_INDEX_ON_FK.format(EXCLUDED_SCHEMAS_STR, table[0], table[1]) + ) uri = f"{db.database}.{table[0]}.{table[1]}" if index_count: for elt in index_count: diff --git a/dblinter/rules/T005/TableWithPotentialMissingIdx.py b/dblinter/rules/T005/TableWithPotentialMissingIdx.py index 3d681f4..9b42198 100644 --- a/dblinter/rules/T005/TableWithPotentialMissingIdx.py +++ b/dblinter/rules/T005/TableWithPotentialMissingIdx.py @@ -1,7 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection -from dblinter.function_library import extract_param +from dblinter.function_library import extract_param,EXCLUDED_SCHEMAS_STR LOGGER = logging.getLogger("dblinter") @@ -20,7 +20,7 @@ def table_with_missing_index( seq_tup_read / seq_scan AS avg FROM pg_stat_user_tables WHERE seq_scan > 0 and - schemaname='{table[0]}' and relname='{table[1]}' + schemaname='{table[0]}' and relname='{table[1]}' and schemaname NOT IN ('{EXCLUDED_SCHEMAS_STR}') """ uri = f"{db.database}.{table[0]}.{table[1]}" threshold = int(extract_param(param, "threshold")) diff --git a/dblinter/rules/T008/TableWithFkMismatch.py b/dblinter/rules/T008/TableWithFkMismatch.py index d31e51f..a982766 100644 --- a/dblinter/rules/T008/TableWithFkMismatch.py +++ b/dblinter/rules/T008/TableWithFkMismatch.py @@ -1,6 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection +from dblinter.function_library import EXCLUDED_SCHEMAS_STR LOGGER = logging.getLogger("dblinter") @@ -42,6 +43,7 @@ def table_with_fk_type_mixmatch( where cls.relname = '{table[1]}' and ns.nspname='{table[0]}' + and ns.nspname NOT IN ('{EXCLUDED_SCHEMAS_STR}') and contype = 'f' ) sub join pg_attribute as ta on diff --git a/dblinter/rules/T009/TableWithRoleNotGranted.py b/dblinter/rules/T009/TableWithRoleNotGranted.py index 4fc64a8..3fe8de3 100644 --- a/dblinter/rules/T009/TableWithRoleNotGranted.py +++ b/dblinter/rules/T009/TableWithRoleNotGranted.py @@ -1,6 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection +from dblinter.function_library import EXCLUDED_SCHEMAS_STR LOGGER = logging.getLogger("dblinter") @@ -16,7 +17,7 @@ def table_with_role_not_granted( ) if table[0] != "public": - TABLE_WITH_ROLE_NOT_GRANTED = f"""SELECT count(*) FROM information_schema.role_table_grants tg inner join pg_roles pgr on pgr.rolname=tg.grantee WHERE table_schema='{table[0]}' and table_name='{table[1]}' and pgr.rolcanlogin=false""" + TABLE_WITH_ROLE_NOT_GRANTED = f"""SELECT count(*) FROM information_schema.role_table_grants tg inner join pg_roles pgr on pgr.rolname=tg.grantee WHERE table_schema NOT IN ('{EXCLUDED_SCHEMAS_STR}') and table_schema='{table[0]}' and table_name='{table[1]}' and pgr.rolcanlogin=false""" table_with_roles = db.query(TABLE_WITH_ROLE_NOT_GRANTED)[0][0] uri = f"{db.database}.{table[0]}.{table[1]}" if table_with_roles == 0: diff --git a/dblinter/rules/T010/ReservedKeyWord.py b/dblinter/rules/T010/ReservedKeyWord.py index e36e095..0333d92 100644 --- a/dblinter/rules/T010/ReservedKeyWord.py +++ b/dblinter/rules/T010/ReservedKeyWord.py @@ -1,6 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection +from dblinter.function_library import EXCLUDED_SCHEMAS_STR LOGGER = logging.getLogger("dblinter") @@ -134,6 +135,7 @@ def reserved_keyword(self, db: DatabaseConnection, _, context, table, sarif_docu pn.oid = pc.relnamespace WHERE pn.nspname = '{table[0]}' + AND pn.nspname NOT IN ('{EXCLUDED_SCHEMAS_STR}') AND relname = '{table[1]}' AND attnum > 0 """ diff --git a/dblinter/rules/T011/TableWithUppercase.py b/dblinter/rules/T011/TableWithUppercase.py index b9800f2..5d6db76 100644 --- a/dblinter/rules/T011/TableWithUppercase.py +++ b/dblinter/rules/T011/TableWithUppercase.py @@ -1,6 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection +from dblinter.function_library import EXCLUDED_SCHEMAS_STR LOGGER = logging.getLogger("dblinter") @@ -13,7 +14,7 @@ def table_with_uppercase( ) NB_UPPERCASE_COLS = f"""SELECT count(distinct table_name) FROM information_schema.columns - WHERE table_schema='{table[0]}' AND table_name='{table[1]}' + WHERE table_schema NOT IN ('{EXCLUDED_SCHEMAS_STR}') AND table_schema='{table[0]}' AND table_name='{table[1]}' AND (lower(table_name) <> table_name OR lower(column_name) <> column_name) """ From f217e19c10cc2dfa19014ce6ec1d3e6c1accdbf9 Mon Sep 17 00:00:00 2001 From: Pierre-Marie Petit Date: Thu, 13 Nov 2025 15:49:39 +0100 Subject: [PATCH 2/3] exclude schema for t rules --- dblinter/rules/T002/TableWithoutIndex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dblinter/rules/T002/TableWithoutIndex.py b/dblinter/rules/T002/TableWithoutIndex.py index 0775963..f60913c 100644 --- a/dblinter/rules/T002/TableWithoutIndex.py +++ b/dblinter/rules/T002/TableWithoutIndex.py @@ -13,7 +13,7 @@ def table_without_index( "table_without_index for %s.%s in db %s", table[0], table[1], db.database ) NB_TABLE_WITHOUT_INDEX = """SELECT count(*) FROM pg_catalog.pg_indexes WHERE schemaname='{}' AND tablename='{}' - AND schemaname NOT ('{}')""" + AND schemaname NOT IN ('{}')""" uri = f"{db.database}.{table[0]}.{table[1]}" index_count = db.query(NB_TABLE_WITHOUT_INDEX.format(table[0], table[1], EXCLUDED_SCHEMAS_STR))[0][0] From 42ca54d6c37aa613614edb9894bff0e24ff906ab Mon Sep 17 00:00:00 2001 From: Pierre-Marie Petit Date: Thu, 13 Nov 2025 16:05:19 +0100 Subject: [PATCH 3/3] fix linter --- dblinter/rules/T002/TableWithoutIndex.py | 4 +++- dblinter/rules/T005/TableWithPotentialMissingIdx.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dblinter/rules/T002/TableWithoutIndex.py b/dblinter/rules/T002/TableWithoutIndex.py index f60913c..ac0c7bc 100644 --- a/dblinter/rules/T002/TableWithoutIndex.py +++ b/dblinter/rules/T002/TableWithoutIndex.py @@ -16,7 +16,9 @@ def table_without_index( AND schemaname NOT IN ('{}')""" uri = f"{db.database}.{table[0]}.{table[1]}" - index_count = db.query(NB_TABLE_WITHOUT_INDEX.format(table[0], table[1], EXCLUDED_SCHEMAS_STR))[0][0] + index_count = db.query( + NB_TABLE_WITHOUT_INDEX.format(table[0], table[1], EXCLUDED_SCHEMAS_STR) + )[0][0] if index_count == 0: message_args = (db.database, table[0], table[1]) sarif_document.add_check( diff --git a/dblinter/rules/T005/TableWithPotentialMissingIdx.py b/dblinter/rules/T005/TableWithPotentialMissingIdx.py index 9b42198..3471175 100644 --- a/dblinter/rules/T005/TableWithPotentialMissingIdx.py +++ b/dblinter/rules/T005/TableWithPotentialMissingIdx.py @@ -1,7 +1,7 @@ import logging from dblinter.database_connection import DatabaseConnection -from dblinter.function_library import extract_param,EXCLUDED_SCHEMAS_STR +from dblinter.function_library import EXCLUDED_SCHEMAS_STR, extract_param LOGGER = logging.getLogger("dblinter")