safegres 1.28.3 → 1.28.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -44,6 +44,11 @@ export declare function checkUnindexedForeignKeys(table: TableIndexSnapshot, pat
44
44
  * leading-column prefix of a wider index. Both cost write throughput and
45
45
  * disk for nothing. Constraint-backed indexes (PK/UNIQUE/EXCLUDE) are never
46
46
  * reported — dropping them would drop the constraint.
47
+ *
48
+ * An index attached to a parent's partitioned index is never reported either:
49
+ * it can only be dropped with the parent's index, and the parent carries that
50
+ * finding once instead of every partition repeating it. An index created on
51
+ * the partition alone is the partition's own, and is reported there.
47
52
  */
48
53
  export declare function checkRedundantIndexes(table: TableIndexSnapshot): Finding[];
49
54
  /**
package/checks/indexes.js CHANGED
@@ -69,10 +69,15 @@ function checkUnindexedForeignKeys(table, paths, onWriteOncePointer = 'report')
69
69
  * leading-column prefix of a wider index. Both cost write throughput and
70
70
  * disk for nothing. Constraint-backed indexes (PK/UNIQUE/EXCLUDE) are never
71
71
  * reported — dropping them would drop the constraint.
72
+ *
73
+ * An index attached to a parent's partitioned index is never reported either:
74
+ * it can only be dropped with the parent's index, and the parent carries that
75
+ * finding once instead of every partition repeating it. An index created on
76
+ * the partition alone is the partition's own, and is reported there.
72
77
  */
73
78
  function checkRedundantIndexes(table) {
74
79
  const findings = [];
75
- const candidates = table.indexes.filter((i) => !i.primary && !i.constraint && !i.partial && !i.expression && !i.unique);
80
+ const candidates = new Set(table.indexes.filter((i) => !i.primary && !i.constraint && !i.partial && !i.expression && !i.unique && !i.attached));
76
81
  for (const idx of candidates) {
77
82
  const covering = table.indexes.find((other) => {
78
83
  if (other.name === idx.name)
@@ -83,8 +88,11 @@ function checkRedundantIndexes(table) {
83
88
  return false;
84
89
  if (!isPrefix(idx.columns, other.columns))
85
90
  return false;
86
- // Exact duplicates: report only one of the pair, deterministically.
87
- if (idx.columns.length === other.columns.length && idx.name < other.name)
91
+ // Exact duplicates: when both are droppable, report only one of the
92
+ // pair, deterministically.
93
+ if (idx.columns.length === other.columns.length
94
+ && candidates.has(other)
95
+ && idx.name < other.name)
88
96
  return false;
89
97
  return true;
90
98
  });
@@ -44,6 +44,11 @@ export declare function checkUnindexedForeignKeys(table: TableIndexSnapshot, pat
44
44
  * leading-column prefix of a wider index. Both cost write throughput and
45
45
  * disk for nothing. Constraint-backed indexes (PK/UNIQUE/EXCLUDE) are never
46
46
  * reported — dropping them would drop the constraint.
47
+ *
48
+ * An index attached to a parent's partitioned index is never reported either:
49
+ * it can only be dropped with the parent's index, and the parent carries that
50
+ * finding once instead of every partition repeating it. An index created on
51
+ * the partition alone is the partition's own, and is reported there.
47
52
  */
48
53
  export declare function checkRedundantIndexes(table: TableIndexSnapshot): Finding[];
49
54
  /**
@@ -62,10 +62,15 @@ export function checkUnindexedForeignKeys(table, paths, onWriteOncePointer = 're
62
62
  * leading-column prefix of a wider index. Both cost write throughput and
63
63
  * disk for nothing. Constraint-backed indexes (PK/UNIQUE/EXCLUDE) are never
64
64
  * reported — dropping them would drop the constraint.
65
+ *
66
+ * An index attached to a parent's partitioned index is never reported either:
67
+ * it can only be dropped with the parent's index, and the parent carries that
68
+ * finding once instead of every partition repeating it. An index created on
69
+ * the partition alone is the partition's own, and is reported there.
65
70
  */
66
71
  export function checkRedundantIndexes(table) {
67
72
  const findings = [];
68
- const candidates = table.indexes.filter((i) => !i.primary && !i.constraint && !i.partial && !i.expression && !i.unique);
73
+ const candidates = new Set(table.indexes.filter((i) => !i.primary && !i.constraint && !i.partial && !i.expression && !i.unique && !i.attached));
69
74
  for (const idx of candidates) {
70
75
  const covering = table.indexes.find((other) => {
71
76
  if (other.name === idx.name)
@@ -76,8 +81,11 @@ export function checkRedundantIndexes(table) {
76
81
  return false;
77
82
  if (!isPrefix(idx.columns, other.columns))
78
83
  return false;
79
- // Exact duplicates: report only one of the pair, deterministically.
80
- if (idx.columns.length === other.columns.length && idx.name < other.name)
84
+ // Exact duplicates: when both are droppable, report only one of the
85
+ // pair, deterministically.
86
+ if (idx.columns.length === other.columns.length
87
+ && candidates.has(other)
88
+ && idx.name < other.name)
81
89
  return false;
82
90
  return true;
83
91
  });
@@ -26,6 +26,12 @@ export interface IndexInfo {
26
26
  /** Access method (`btree`, `gin`, …). */
27
27
  method: string;
28
28
  definition: string;
29
+ /**
30
+ * A partition of an index on the parent partitioned table
31
+ * (`pg_class.relispartition` on the index). Such an index cannot be dropped
32
+ * on its own; it exists because the parent's index does.
33
+ */
34
+ attached: boolean;
29
35
  }
30
36
  export interface ColumnInfo {
31
37
  name: string;
package/esm/pg/indexes.js CHANGED
@@ -49,7 +49,8 @@ export async function introspectIndexes(exec, options = {}) {
49
49
  (ix.indpred IS NOT NULL) AS is_partial,
50
50
  (ix.indexprs IS NOT NULL) AS is_expression,
51
51
  am.amname AS method,
52
- pg_get_indexdef(i.oid) AS definition
52
+ pg_get_indexdef(i.oid) AS definition,
53
+ i.relispartition AS is_attached
53
54
  FROM pg_index ix
54
55
  JOIN pg_class i ON i.oid = ix.indexrelid
55
56
  JOIN pg_am am ON am.oid = i.relam
@@ -84,7 +85,8 @@ export async function introspectIndexes(exec, options = {}) {
84
85
  'partial', x.is_partial,
85
86
  'expression', x.is_expression,
86
87
  'method', x.method,
87
- 'definition', x.definition
88
+ 'definition', x.definition,
89
+ 'attached', x.is_attached
88
90
  ) ORDER BY x.name) FROM indexes x WHERE x.oid = r.oid),
89
91
  '[]'::jsonb
90
92
  ) AS indexes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safegres",
3
- "version": "1.28.3",
3
+ "version": "1.28.5",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Postgres security and performance auditor: RLS, grants and effective-access analysis plus index and policy-cost checks, scored on two independent axes. One connection, no framework.",
6
6
  "main": "index.js",
@@ -83,7 +83,7 @@
83
83
  "@types/node": "^22.0.0",
84
84
  "@types/pg": "^8.20.4",
85
85
  "makage": "^0.8.0",
86
- "pgsql-test": "^5.17.0"
86
+ "pgsql-test": "^5.17.1"
87
87
  },
88
- "gitHead": "4c68f85c7d9b3c72082f8305dffc0b3dfca80941"
88
+ "gitHead": "34e91da5b3dd9c0cae14875afe609993acf53a84"
89
89
  }
package/pg/indexes.d.ts CHANGED
@@ -26,6 +26,12 @@ export interface IndexInfo {
26
26
  /** Access method (`btree`, `gin`, …). */
27
27
  method: string;
28
28
  definition: string;
29
+ /**
30
+ * A partition of an index on the parent partitioned table
31
+ * (`pg_class.relispartition` on the index). Such an index cannot be dropped
32
+ * on its own; it exists because the parent's index does.
33
+ */
34
+ attached: boolean;
29
35
  }
30
36
  export interface ColumnInfo {
31
37
  name: string;
package/pg/indexes.js CHANGED
@@ -54,7 +54,8 @@ async function introspectIndexes(exec, options = {}) {
54
54
  (ix.indpred IS NOT NULL) AS is_partial,
55
55
  (ix.indexprs IS NOT NULL) AS is_expression,
56
56
  am.amname AS method,
57
- pg_get_indexdef(i.oid) AS definition
57
+ pg_get_indexdef(i.oid) AS definition,
58
+ i.relispartition AS is_attached
58
59
  FROM pg_index ix
59
60
  JOIN pg_class i ON i.oid = ix.indexrelid
60
61
  JOIN pg_am am ON am.oid = i.relam
@@ -89,7 +90,8 @@ async function introspectIndexes(exec, options = {}) {
89
90
  'partial', x.is_partial,
90
91
  'expression', x.is_expression,
91
92
  'method', x.method,
92
- 'definition', x.definition
93
+ 'definition', x.definition,
94
+ 'attached', x.is_attached
93
95
  ) ORDER BY x.name) FROM indexes x WHERE x.oid = r.oid),
94
96
  '[]'::jsonb
95
97
  ) AS indexes,