schema-navigator-mcp 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +215 -0
- package/dist/db/pg-adapter.d.ts +17 -0
- package/dist/db/pg-adapter.js +74 -0
- package/dist/db/pg-adapter.js.map +1 -0
- package/dist/db/postgres.d.ts +19 -0
- package/dist/db/postgres.js +34 -0
- package/dist/db/postgres.js.map +1 -0
- package/dist/db/supabase-adapter.d.ts +45 -0
- package/dist/db/supabase-adapter.js +148 -0
- package/dist/db/supabase-adapter.js.map +1 -0
- package/dist/db/types.d.ts +25 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +129 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/constraints.d.ts +22 -0
- package/dist/tools/constraints.js +80 -0
- package/dist/tools/constraints.js.map +1 -0
- package/dist/tools/functions.d.ts +20 -0
- package/dist/tools/functions.js +118 -0
- package/dist/tools/functions.js.map +1 -0
- package/dist/tools/overview.d.ts +29 -0
- package/dist/tools/overview.js +142 -0
- package/dist/tools/overview.js.map +1 -0
- package/dist/tools/policies.d.ts +29 -0
- package/dist/tools/policies.js +96 -0
- package/dist/tools/policies.js.map +1 -0
- package/dist/tools/relations.d.ts +18 -0
- package/dist/tools/relations.js +72 -0
- package/dist/tools/relations.js.map +1 -0
- package/dist/tools/search.d.ts +13 -0
- package/dist/tools/search.js +77 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/table-profile.d.ts +68 -0
- package/dist/tools/table-profile.js +477 -0
- package/dist/tools/table-profile.js.map +1 -0
- package/dist/utils/check-parser.d.ts +17 -0
- package/dist/utils/check-parser.js +61 -0
- package/dist/utils/check-parser.js.map +1 -0
- package/dist/utils/pg-array.d.ts +6 -0
- package/dist/utils/pg-array.js +42 -0
- package/dist/utils/pg-array.js.map +1 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createSchemaNavigator } from "./server.js";
|
|
4
|
+
import { shutdownPool } from "./db/postgres.js";
|
|
5
|
+
import { extractRefFromUrl } from "./db/supabase-adapter.js";
|
|
6
|
+
function resolveConfig() {
|
|
7
|
+
// Priority 1: Direct database connection (any PostgreSQL)
|
|
8
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
9
|
+
if (databaseUrl) {
|
|
10
|
+
if (!/^postgres(ql)?:\/\//i.test(databaseUrl)) {
|
|
11
|
+
console.error("Error: DATABASE_URL must be a postgresql:// connection string");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
return databaseUrl;
|
|
15
|
+
}
|
|
16
|
+
// Priority 2: Supabase Management API (no password needed)
|
|
17
|
+
const accessToken = process.env.SUPABASE_ACCESS_TOKEN;
|
|
18
|
+
if (accessToken) {
|
|
19
|
+
const projectRef = resolveProjectRef();
|
|
20
|
+
if (!projectRef) {
|
|
21
|
+
console.error("Error: SUPABASE_ACCESS_TOKEN is set but no project reference found.\n" +
|
|
22
|
+
"Set one of:\n" +
|
|
23
|
+
" SUPABASE_PROJECT_REF=your-project-ref\n" +
|
|
24
|
+
" SUPABASE_URL=https://your-project-ref.supabase.co\n" +
|
|
25
|
+
" NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
return { accessToken, projectRef };
|
|
29
|
+
}
|
|
30
|
+
// No config found — give context-aware error
|
|
31
|
+
const hasSupabaseVars = !!(process.env.SUPABASE_URL ||
|
|
32
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL ||
|
|
33
|
+
process.env.SUPABASE_PROJECT_REF);
|
|
34
|
+
if (hasSupabaseVars) {
|
|
35
|
+
console.error("Error: Found Supabase project info but no credentials.\n" +
|
|
36
|
+
"Either:\n" +
|
|
37
|
+
" 1. Set DATABASE_URL for direct connection (requires db password)\n" +
|
|
38
|
+
" 2. Set SUPABASE_ACCESS_TOKEN for Management API (no password needed)\n" +
|
|
39
|
+
" Get a token at: https://supabase.com/dashboard/account/tokens");
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error("Error: No database connection configured.\n\n" +
|
|
43
|
+
"Option 1 — Direct connection (any PostgreSQL):\n" +
|
|
44
|
+
" DATABASE_URL=postgresql://user:pass@host:5432/db\n\n" +
|
|
45
|
+
"Option 2 — Supabase (no password needed):\n" +
|
|
46
|
+
" SUPABASE_ACCESS_TOKEN=sbp_... (from supabase.com/dashboard/account/tokens)\n" +
|
|
47
|
+
" SUPABASE_PROJECT_REF=your-project-ref (from your dashboard URL)");
|
|
48
|
+
}
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extract project ref from available env vars.
|
|
53
|
+
*/
|
|
54
|
+
function resolveProjectRef() {
|
|
55
|
+
if (process.env.SUPABASE_PROJECT_REF) {
|
|
56
|
+
return process.env.SUPABASE_PROJECT_REF;
|
|
57
|
+
}
|
|
58
|
+
const url = process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
59
|
+
if (url) {
|
|
60
|
+
return extractRefFromUrl(url);
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
async function main() {
|
|
65
|
+
const config = resolveConfig();
|
|
66
|
+
const server = await createSchemaNavigator(config);
|
|
67
|
+
const transport = new StdioServerTransport();
|
|
68
|
+
const shutdown = async () => {
|
|
69
|
+
await shutdownPool();
|
|
70
|
+
process.exit(0);
|
|
71
|
+
};
|
|
72
|
+
process.on("SIGINT", shutdown);
|
|
73
|
+
process.on("SIGTERM", shutdown);
|
|
74
|
+
await server.connect(transport);
|
|
75
|
+
}
|
|
76
|
+
main().catch((err) => {
|
|
77
|
+
console.error("Fatal error:", err instanceof Error ? err.message : err);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,SAAS,aAAa;IACpB,0DAA0D;IAC1D,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAC7C,IAAI,WAAW,EAAE,CAAC;QAChB,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAC9C,OAAO,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;YAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,2DAA2D;IAC3D,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IACtD,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;QACvC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CACX,uEAAuE;gBACrE,eAAe;gBACf,2CAA2C;gBAC3C,uDAAuD;gBACvD,iEAAiE,CACpE,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IACrC,CAAC;IAED,6CAA6C;IAC7C,MAAM,eAAe,GAAG,CAAC,CAAC,CACxB,OAAO,CAAC,GAAG,CAAC,YAAY;QACxB,OAAO,CAAC,GAAG,CAAC,wBAAwB;QACpC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CACjC,CAAC;IAEF,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CACX,0DAA0D;YACxD,WAAW;YACX,sEAAsE;YACtE,0EAA0E;YAC1E,oEAAoE,CACvE,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CACX,+CAA+C;YAC7C,kDAAkD;YAClD,wDAAwD;YACxD,6CAA6C;YAC7C,iFAAiF;YACjF,oEAAoE,CACvE,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB;IACxB,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC;QACrC,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAC1C,CAAC;IACD,MAAM,GAAG,GACP,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IACnE,IAAI,GAAG,EAAE,CAAC;QACR,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAE7C,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,MAAM,YAAY,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAEhC,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { SupabaseAdapterConfig } from "./db/supabase-adapter.js";
|
|
3
|
+
export type SchemaNavigatorConfig = string | SupabaseAdapterConfig;
|
|
4
|
+
export declare function createSchemaNavigator(config: SchemaNavigatorConfig): Promise<McpServer>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { setAdapter, shutdownPool } from "./db/postgres.js";
|
|
4
|
+
import { PgAdapter } from "./db/pg-adapter.js";
|
|
5
|
+
import { SupabaseAdapter } from "./db/supabase-adapter.js";
|
|
6
|
+
import { schemaOverview } from "./tools/overview.js";
|
|
7
|
+
import { schemaTable } from "./tools/table-profile.js";
|
|
8
|
+
import { schemaFind } from "./tools/search.js";
|
|
9
|
+
import { schemaRelations } from "./tools/relations.js";
|
|
10
|
+
import { schemaFunctions } from "./tools/functions.js";
|
|
11
|
+
import { schemaConstraints } from "./tools/constraints.js";
|
|
12
|
+
import { schemaPolicies } from "./tools/policies.js";
|
|
13
|
+
function errorMessage(err) {
|
|
14
|
+
return err instanceof Error ? err.message : String(err);
|
|
15
|
+
}
|
|
16
|
+
function toolError(err) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: JSON.stringify({ error: errorMessage(err) }) }],
|
|
19
|
+
isError: true,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export async function createSchemaNavigator(config) {
|
|
23
|
+
if (typeof config === "string") {
|
|
24
|
+
const pgAdapter = new PgAdapter(config);
|
|
25
|
+
await pgAdapter.detectVersion();
|
|
26
|
+
setAdapter(pgAdapter);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
setAdapter(new SupabaseAdapter(config));
|
|
30
|
+
}
|
|
31
|
+
const server = new McpServer({
|
|
32
|
+
name: "schema-navigator",
|
|
33
|
+
version: "0.2.0",
|
|
34
|
+
});
|
|
35
|
+
// 1. schema_overview — Database dashboard
|
|
36
|
+
server.tool("schema_overview", "List all tables, views, enums, extensions with sizes and health warnings", { schema: z.string().default("public").describe("Database schema name") }, async ({ schema }) => {
|
|
37
|
+
try {
|
|
38
|
+
const result = await schemaOverview(schema);
|
|
39
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return toolError(err);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// 2. schema_table — Complete object profile
|
|
46
|
+
server.tool("schema_table", "Profile a table/view/matview: columns, constraints, FKs, indexes, RLS, size", {
|
|
47
|
+
table_name: z.string().describe("Table, view, or materialized view name"),
|
|
48
|
+
schema: z.string().default("public").describe("Database schema name"),
|
|
49
|
+
}, async ({ table_name, schema }) => {
|
|
50
|
+
try {
|
|
51
|
+
const result = await schemaTable(table_name, schema);
|
|
52
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
return toolError(err);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// 3. schema_find — Cross-schema search
|
|
59
|
+
server.tool("schema_find", "Search tables, columns, functions, views, policies, enums by name pattern", {
|
|
60
|
+
term: z.string().describe("Search term (case-insensitive partial match)"),
|
|
61
|
+
schema: z.string().default("public").describe("Database schema name"),
|
|
62
|
+
}, async ({ term, schema }) => {
|
|
63
|
+
try {
|
|
64
|
+
const result = await schemaFind(term, schema);
|
|
65
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return toolError(err);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// 4. schema_relations — FK graph
|
|
72
|
+
server.tool("schema_relations", "Show foreign key relationships: outbound and inbound references", {
|
|
73
|
+
table_name: z.string().describe("Table name to get relations for"),
|
|
74
|
+
schema: z.string().default("public").describe("Database schema name"),
|
|
75
|
+
}, async ({ table_name, schema }) => {
|
|
76
|
+
try {
|
|
77
|
+
const result = await schemaRelations(table_name, schema);
|
|
78
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
return toolError(err);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// 5. schema_functions — Function inspector
|
|
85
|
+
server.tool("schema_functions", "Inspect function: args, return type, language, security, source code", {
|
|
86
|
+
function_name: z.string().describe("Function or procedure name"),
|
|
87
|
+
schema: z.string().default("public").describe("Database schema name"),
|
|
88
|
+
}, async ({ function_name, schema }) => {
|
|
89
|
+
try {
|
|
90
|
+
const result = await schemaFunctions(function_name, schema);
|
|
91
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
return toolError(err);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
// 6. schema_constraints — Value extractor (CHECK + ENUM)
|
|
98
|
+
server.tool("schema_constraints", "Extract allowed values from CHECK constraints and ENUM types on a table", {
|
|
99
|
+
table_name: z.string().describe("Table name"),
|
|
100
|
+
schema: z.string().default("public").describe("Database schema name"),
|
|
101
|
+
}, async ({ table_name, schema }) => {
|
|
102
|
+
try {
|
|
103
|
+
const result = await schemaConstraints(table_name, schema);
|
|
104
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return toolError(err);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// 7. schema_policies — RLS analyzer
|
|
111
|
+
server.tool("schema_policies", "Analyze RLS: enabled status, policies, role access matrix", {
|
|
112
|
+
table_name: z.string().describe("Table name"),
|
|
113
|
+
schema: z.string().default("public").describe("Database schema name"),
|
|
114
|
+
}, async ({ table_name, schema }) => {
|
|
115
|
+
try {
|
|
116
|
+
const result = await schemaPolicies(table_name, schema);
|
|
117
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
return toolError(err);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// Cleanup on close
|
|
124
|
+
server.server.onclose = async () => {
|
|
125
|
+
await shutdownPool();
|
|
126
|
+
};
|
|
127
|
+
return server;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface CheckConstraint {
|
|
2
|
+
constraint_name: string;
|
|
3
|
+
column: string | null;
|
|
4
|
+
type: "values" | "range" | "unknown";
|
|
5
|
+
allowed_values?: string[];
|
|
6
|
+
expression: string;
|
|
7
|
+
}
|
|
8
|
+
interface EnumColumn {
|
|
9
|
+
column: string;
|
|
10
|
+
enum_name: string;
|
|
11
|
+
values: string[];
|
|
12
|
+
}
|
|
13
|
+
interface ConstraintsResult {
|
|
14
|
+
table: string;
|
|
15
|
+
schema: string;
|
|
16
|
+
check_constraints: CheckConstraint[];
|
|
17
|
+
enum_columns: EnumColumn[];
|
|
18
|
+
}
|
|
19
|
+
export declare function schemaConstraints(tableName: string, schema: string): Promise<ConstraintsResult | {
|
|
20
|
+
error: string;
|
|
21
|
+
}>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readOnlyMultiQuery, getIsPostgres } from "../db/postgres.js";
|
|
2
|
+
import { parseCheckExpression } from "../utils/check-parser.js";
|
|
3
|
+
export async function schemaConstraints(tableName, schema) {
|
|
4
|
+
const isPg = getIsPostgres();
|
|
5
|
+
const queries = [
|
|
6
|
+
// Check table existence
|
|
7
|
+
{
|
|
8
|
+
sql: `SELECT 1 FROM information_schema.tables WHERE table_schema = $1 AND table_name = $2`,
|
|
9
|
+
params: [schema, tableName],
|
|
10
|
+
},
|
|
11
|
+
// CHECK constraints
|
|
12
|
+
{
|
|
13
|
+
sql: `
|
|
14
|
+
SELECT tc.constraint_name, cc.check_clause
|
|
15
|
+
FROM information_schema.table_constraints tc
|
|
16
|
+
JOIN information_schema.check_constraints cc
|
|
17
|
+
ON cc.constraint_name = tc.constraint_name AND cc.constraint_schema = tc.constraint_schema
|
|
18
|
+
WHERE tc.table_schema = $1 AND tc.table_name = $2 AND tc.constraint_type = 'CHECK'
|
|
19
|
+
ORDER BY tc.constraint_name
|
|
20
|
+
`,
|
|
21
|
+
params: [schema, tableName],
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
// ENUM columns (Postgres only)
|
|
25
|
+
if (isPg) {
|
|
26
|
+
queries.push({
|
|
27
|
+
sql: `
|
|
28
|
+
SELECT
|
|
29
|
+
a.attname AS column_name,
|
|
30
|
+
t.typname AS enum_name,
|
|
31
|
+
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS enum_values
|
|
32
|
+
FROM pg_attribute a
|
|
33
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
34
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
35
|
+
JOIN pg_type t ON t.oid = a.atttypid
|
|
36
|
+
JOIN pg_enum e ON e.enumtypid = t.oid
|
|
37
|
+
WHERE n.nspname = $1 AND c.relname = $2
|
|
38
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
39
|
+
GROUP BY a.attname, t.typname
|
|
40
|
+
ORDER BY a.attname
|
|
41
|
+
`,
|
|
42
|
+
params: [schema, tableName],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const results = await readOnlyMultiQuery(queries);
|
|
46
|
+
if (results[0].rows.length === 0) {
|
|
47
|
+
return { error: `Table '${tableName}' not found in schema '${schema}'` };
|
|
48
|
+
}
|
|
49
|
+
// Parse CHECK constraints
|
|
50
|
+
const checkConstraints = results[1].rows
|
|
51
|
+
.map((r) => {
|
|
52
|
+
const parsed = parseCheckExpression(r.check_clause);
|
|
53
|
+
// Skip auto-generated NOT NULL checks
|
|
54
|
+
if (parsed.column === null && parsed.type === "unknown")
|
|
55
|
+
return null;
|
|
56
|
+
return {
|
|
57
|
+
constraint_name: r.constraint_name,
|
|
58
|
+
column: parsed.column,
|
|
59
|
+
type: parsed.type,
|
|
60
|
+
...(parsed.allowed_values ? { allowed_values: parsed.allowed_values } : {}),
|
|
61
|
+
expression: r.check_clause,
|
|
62
|
+
};
|
|
63
|
+
})
|
|
64
|
+
.filter((c) => c !== null);
|
|
65
|
+
// ENUM columns
|
|
66
|
+
const enumColumns = isPg
|
|
67
|
+
? results[2].rows.map((r) => ({
|
|
68
|
+
column: r.column_name,
|
|
69
|
+
enum_name: r.enum_name,
|
|
70
|
+
values: r.enum_values,
|
|
71
|
+
}))
|
|
72
|
+
: [];
|
|
73
|
+
return {
|
|
74
|
+
table: tableName,
|
|
75
|
+
schema,
|
|
76
|
+
check_constraints: checkConstraints,
|
|
77
|
+
enum_columns: enumColumns,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=constraints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constraints.js","sourceRoot":"","sources":["../../src/tools/constraints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAuBhE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAiB,EACjB,MAAc;IAEd,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;IAE7B,MAAM,OAAO,GAA+C;QAC1D,wBAAwB;QACxB;YACE,GAAG,EAAE,qFAAqF;YAC1F,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;SAC5B;QACD,oBAAoB;QACpB;YACE,GAAG,EAAE;;;;;;;OAOJ;YACD,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;SAC5B;KACF,CAAC;IAEF,+BAA+B;IAC/B,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,CAAC,IAAI,CAAC;YACX,GAAG,EAAE;;;;;;;;;;;;;;OAcJ;YACD,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAElD,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,KAAK,EAAE,UAAU,SAAS,0BAA0B,MAAM,GAAG,EAAE,CAAC;IAC3E,CAAC;IAED,0BAA0B;IAC1B,MAAM,gBAAgB,GAAsB,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI;SACxD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,MAAM,GAAG,oBAAoB,CAAC,CAAC,CAAC,YAAsB,CAAC,CAAC;QAC9D,sCAAsC;QACtC,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACrE,OAAO;YACL,eAAe,EAAE,CAAC,CAAC,eAAyB;YAC5C,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,UAAU,EAAE,CAAC,CAAC,YAAsB;SACrC,CAAC;IACJ,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAwB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAEnD,eAAe;IACf,MAAM,WAAW,GAAiB,IAAI;QACpC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1B,MAAM,EAAE,CAAC,CAAC,WAAqB;YAC/B,SAAS,EAAE,CAAC,CAAC,SAAmB;YAChC,MAAM,EAAE,CAAC,CAAC,WAAuB;SAClC,CAAC,CAAC;QACL,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;QACL,KAAK,EAAE,SAAS;QAChB,MAAM;QACN,iBAAiB,EAAE,gBAAgB;QACnC,YAAY,EAAE,WAAW;KAC1B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface FunctionArg {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
mode: string;
|
|
5
|
+
default: string | null;
|
|
6
|
+
}
|
|
7
|
+
interface FunctionResult {
|
|
8
|
+
name: string;
|
|
9
|
+
schema: string;
|
|
10
|
+
args: FunctionArg[];
|
|
11
|
+
return_type: string;
|
|
12
|
+
language: string;
|
|
13
|
+
security_definer: boolean;
|
|
14
|
+
volatility: string | null;
|
|
15
|
+
source_code: string | null;
|
|
16
|
+
}
|
|
17
|
+
export declare function schemaFunctions(functionName: string, schema: string): Promise<FunctionResult[] | {
|
|
18
|
+
error: string;
|
|
19
|
+
}>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readOnlyQuery, getIsPostgres } from "../db/postgres.js";
|
|
2
|
+
export async function schemaFunctions(functionName, schema) {
|
|
3
|
+
const isPg = getIsPostgres();
|
|
4
|
+
if (isPg) {
|
|
5
|
+
// Full function details via pg_catalog
|
|
6
|
+
const result = await readOnlyQuery(`
|
|
7
|
+
SELECT
|
|
8
|
+
p.proname AS name,
|
|
9
|
+
pg_get_function_arguments(p.oid) AS args_text,
|
|
10
|
+
pg_get_function_result(p.oid) AS return_type,
|
|
11
|
+
l.lanname AS language,
|
|
12
|
+
p.prosecdef AS security_definer,
|
|
13
|
+
CASE p.provolatile
|
|
14
|
+
WHEN 'i' THEN 'immutable'
|
|
15
|
+
WHEN 's' THEN 'stable'
|
|
16
|
+
WHEN 'v' THEN 'volatile'
|
|
17
|
+
END AS volatility,
|
|
18
|
+
pg_get_functiondef(p.oid) AS source_code
|
|
19
|
+
FROM pg_proc p
|
|
20
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
21
|
+
JOIN pg_language l ON l.oid = p.prolang
|
|
22
|
+
WHERE n.nspname = $1 AND p.proname = $2
|
|
23
|
+
ORDER BY p.pronargs
|
|
24
|
+
`, [schema, functionName]);
|
|
25
|
+
if (result.rows.length === 0) {
|
|
26
|
+
return { error: `Function '${functionName}' not found in schema '${schema}'` };
|
|
27
|
+
}
|
|
28
|
+
return result.rows.map((r) => ({
|
|
29
|
+
name: r.name,
|
|
30
|
+
schema,
|
|
31
|
+
args: parseArgsText(r.args_text),
|
|
32
|
+
return_type: r.return_type,
|
|
33
|
+
language: r.language,
|
|
34
|
+
security_definer: r.security_definer,
|
|
35
|
+
volatility: r.volatility,
|
|
36
|
+
source_code: r.source_code,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
// Fallback: information_schema (signature only, no source)
|
|
40
|
+
const result = await readOnlyQuery(`
|
|
41
|
+
SELECT
|
|
42
|
+
r.routine_name AS name,
|
|
43
|
+
r.data_type AS return_type,
|
|
44
|
+
r.routine_body AS language,
|
|
45
|
+
r.security_type
|
|
46
|
+
FROM information_schema.routines r
|
|
47
|
+
WHERE r.routine_schema = $1 AND r.routine_name = $2
|
|
48
|
+
ORDER BY r.routine_name
|
|
49
|
+
`, [schema, functionName]);
|
|
50
|
+
if (result.rows.length === 0) {
|
|
51
|
+
return { error: `Function '${functionName}' not found in schema '${schema}'` };
|
|
52
|
+
}
|
|
53
|
+
// Get parameters
|
|
54
|
+
const paramsResult = await readOnlyQuery(`
|
|
55
|
+
SELECT
|
|
56
|
+
p.parameter_name, p.data_type, p.parameter_mode, p.parameter_default
|
|
57
|
+
FROM information_schema.parameters p
|
|
58
|
+
WHERE p.specific_schema = $1
|
|
59
|
+
AND p.specific_name LIKE $2
|
|
60
|
+
ORDER BY p.ordinal_position
|
|
61
|
+
`, [schema, `${functionName}%`]);
|
|
62
|
+
const args = paramsResult.rows.map((r) => ({
|
|
63
|
+
name: r.parameter_name ?? "",
|
|
64
|
+
type: r.data_type,
|
|
65
|
+
mode: r.parameter_mode ?? "IN",
|
|
66
|
+
default: r.parameter_default ?? null,
|
|
67
|
+
}));
|
|
68
|
+
return result.rows.map((r) => ({
|
|
69
|
+
name: r.name,
|
|
70
|
+
schema,
|
|
71
|
+
args,
|
|
72
|
+
return_type: r.return_type,
|
|
73
|
+
language: r.language,
|
|
74
|
+
security_definer: r.security_type === "DEFINER",
|
|
75
|
+
volatility: null,
|
|
76
|
+
source_code: null,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
function parseArgsText(argsText) {
|
|
80
|
+
if (!argsText || argsText.trim() === "")
|
|
81
|
+
return [];
|
|
82
|
+
return argsText.split(",").map((arg) => {
|
|
83
|
+
const trimmed = arg.trim();
|
|
84
|
+
// Format: [mode] [name] type [DEFAULT value]
|
|
85
|
+
const defaultMatch = trimmed.match(/\s+DEFAULT\s+(.+)$/i);
|
|
86
|
+
const withoutDefault = defaultMatch
|
|
87
|
+
? trimmed.slice(0, -defaultMatch[0].length)
|
|
88
|
+
: trimmed;
|
|
89
|
+
const parts = withoutDefault.split(/\s+/);
|
|
90
|
+
let mode = "IN";
|
|
91
|
+
let name = "";
|
|
92
|
+
let type = "";
|
|
93
|
+
if (["IN", "OUT", "INOUT", "VARIADIC"].includes(parts[0].toUpperCase())) {
|
|
94
|
+
mode = parts[0].toUpperCase();
|
|
95
|
+
if (parts.length >= 3) {
|
|
96
|
+
name = parts[1];
|
|
97
|
+
type = parts.slice(2).join(" ");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
type = parts.slice(1).join(" ");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (parts.length >= 2) {
|
|
104
|
+
name = parts[0];
|
|
105
|
+
type = parts.slice(1).join(" ");
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
type = parts[0];
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
name,
|
|
112
|
+
type,
|
|
113
|
+
mode,
|
|
114
|
+
default: defaultMatch ? defaultMatch[1] : null,
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=functions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functions.js","sourceRoot":"","sources":["../../src/tools/functions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAoBjE,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,YAAoB,EACpB,MAAc;IAEd,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;IAE7B,IAAI,IAAI,EAAE,CAAC;QACT,uCAAuC;QACvC,MAAM,MAAM,GAAG,MAAM,aAAa,CAChC;;;;;;;;;;;;;;;;;;OAkBC,EACD,CAAC,MAAM,EAAE,YAAY,CAAC,CACvB,CAAC;QAEF,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,KAAK,EAAE,aAAa,YAAY,0BAA0B,MAAM,GAAG,EAAE,CAAC;QACjF,CAAC;QAED,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,IAAI,EAAE,CAAC,CAAC,IAAc;YACtB,MAAM;YACN,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,SAAmB,CAAC;YAC1C,WAAW,EAAE,CAAC,CAAC,WAAqB;YACpC,QAAQ,EAAE,CAAC,CAAC,QAAkB;YAC9B,gBAAgB,EAAE,CAAC,CAAC,gBAA2B;YAC/C,UAAU,EAAE,CAAC,CAAC,UAAoB;YAClC,WAAW,EAAE,CAAC,CAAC,WAAqB;SACrC,CAAC,CAAC,CAAC;IACN,CAAC;IAED,2DAA2D;IAC3D,MAAM,MAAM,GAAG,MAAM,aAAa,CAChC;;;;;;;;;KASC,EACD,CAAC,MAAM,EAAE,YAAY,CAAC,CACvB,CAAC;IAEF,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,aAAa,YAAY,0BAA0B,MAAM,GAAG,EAAE,CAAC;IACjF,CAAC;IAED,iBAAiB;IACjB,MAAM,YAAY,GAAG,MAAM,aAAa,CACtC;;;;;;;KAOC,EACD,CAAC,MAAM,EAAE,GAAG,YAAY,GAAG,CAAC,CAC7B,CAAC;IAEF,MAAM,IAAI,GAAkB,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,IAAI,EAAG,CAAC,CAAC,cAAyB,IAAI,EAAE;QACxC,IAAI,EAAE,CAAC,CAAC,SAAmB;QAC3B,IAAI,EAAG,CAAC,CAAC,cAAyB,IAAI,IAAI;QAC1C,OAAO,EAAG,CAAC,CAAC,iBAA4B,IAAI,IAAI;KACjD,CAAC,CAAC,CAAC;IAEJ,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC7B,IAAI,EAAE,CAAC,CAAC,IAAc;QACtB,MAAM;QACN,IAAI;QACJ,WAAW,EAAE,CAAC,CAAC,WAAqB;QACpC,QAAQ,EAAE,CAAC,CAAC,QAAkB;QAC9B,gBAAgB,EAAE,CAAC,CAAC,aAAa,KAAK,SAAS;QAC/C,UAAU,EAAE,IAAI;QAChB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB;IACrC,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAEnD,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,6CAA6C;QAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC1D,MAAM,cAAc,GAAG,YAAY;YACjC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAC3C,CAAC,CAAC,OAAO,CAAC;QAEZ,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,IAAI,GAAG,IAAI,CAAC;QAChB,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,IAAI,GAAG,EAAE,CAAC;QAEd,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACxE,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAC9B,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBACtB,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBAChB,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClC,CAAC;iBAAM,CAAC;gBACN,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YAC7B,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAChB,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,OAAO;YACL,IAAI;YACJ,IAAI;YACJ,IAAI;YACJ,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;SAC/C,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface TableInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
rows: number | null;
|
|
5
|
+
size: string | null;
|
|
6
|
+
}
|
|
7
|
+
interface EnumType {
|
|
8
|
+
name: string;
|
|
9
|
+
values: string[];
|
|
10
|
+
}
|
|
11
|
+
interface OverviewResult {
|
|
12
|
+
schema: string;
|
|
13
|
+
tables: TableInfo[];
|
|
14
|
+
views: string[];
|
|
15
|
+
materialized_views: string[];
|
|
16
|
+
enum_types: EnumType[];
|
|
17
|
+
extensions: string[];
|
|
18
|
+
counts: {
|
|
19
|
+
tables: number;
|
|
20
|
+
views: number;
|
|
21
|
+
materialized_views: number;
|
|
22
|
+
functions: number;
|
|
23
|
+
enum_types: number;
|
|
24
|
+
truncated?: boolean;
|
|
25
|
+
};
|
|
26
|
+
health_warnings: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare function schemaOverview(schema: string): Promise<OverviewResult>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readOnlyMultiQuery, getIsPostgres } from "../db/postgres.js";
|
|
2
|
+
export async function schemaOverview(schema) {
|
|
3
|
+
const isPg = getIsPostgres();
|
|
4
|
+
const queries = [];
|
|
5
|
+
// Q0: Tables with row estimates and sizes
|
|
6
|
+
if (isPg) {
|
|
7
|
+
queries.push({
|
|
8
|
+
sql: `
|
|
9
|
+
SELECT
|
|
10
|
+
c.relname AS name,
|
|
11
|
+
CASE c.relkind WHEN 'r' THEN 'table' WHEN 'p' THEN 'partitioned' END AS type,
|
|
12
|
+
c.reltuples::bigint AS rows,
|
|
13
|
+
pg_total_relation_size(c.oid)::bigint AS size_bytes
|
|
14
|
+
FROM pg_class c
|
|
15
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
16
|
+
WHERE n.nspname = $1
|
|
17
|
+
AND c.relkind IN ('r', 'p')
|
|
18
|
+
ORDER BY pg_total_relation_size(c.oid) DESC
|
|
19
|
+
LIMIT 100
|
|
20
|
+
`,
|
|
21
|
+
params: [schema],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
queries.push({
|
|
26
|
+
sql: `SELECT table_name AS name, 'table' AS type, NULL AS rows, NULL AS size_bytes
|
|
27
|
+
FROM information_schema.tables
|
|
28
|
+
WHERE table_schema = $1 AND table_type = 'BASE TABLE'
|
|
29
|
+
ORDER BY table_name LIMIT 100`,
|
|
30
|
+
params: [schema],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Q1: Views
|
|
34
|
+
queries.push({
|
|
35
|
+
sql: `SELECT table_name FROM information_schema.views WHERE table_schema = $1 ORDER BY table_name`,
|
|
36
|
+
params: [schema],
|
|
37
|
+
});
|
|
38
|
+
// Q2: Materialized views (Postgres only, empty on others)
|
|
39
|
+
queries.push(isPg
|
|
40
|
+
? { sql: `SELECT matviewname FROM pg_matviews WHERE schemaname = $1 ORDER BY matviewname`, params: [schema] }
|
|
41
|
+
: { sql: `SELECT null AS matviewname WHERE false` });
|
|
42
|
+
// Q3: ENUMs (Postgres only)
|
|
43
|
+
queries.push(isPg
|
|
44
|
+
? {
|
|
45
|
+
sql: `SELECT t.typname AS name, array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
|
|
46
|
+
FROM pg_type t
|
|
47
|
+
JOIN pg_enum e ON e.enumtypid = t.oid
|
|
48
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
49
|
+
WHERE n.nspname = $1
|
|
50
|
+
GROUP BY t.typname ORDER BY t.typname`,
|
|
51
|
+
params: [schema],
|
|
52
|
+
}
|
|
53
|
+
: { sql: `SELECT null AS name, null AS values WHERE false` });
|
|
54
|
+
// Q4: Extensions (Postgres only, database-wide)
|
|
55
|
+
queries.push(isPg
|
|
56
|
+
? { sql: `SELECT extname FROM pg_extension ORDER BY extname` }
|
|
57
|
+
: { sql: `SELECT null AS extname WHERE false` });
|
|
58
|
+
// Q5: Function count
|
|
59
|
+
queries.push({
|
|
60
|
+
sql: `SELECT COUNT(*)::int AS count FROM information_schema.routines WHERE routine_schema = $1`,
|
|
61
|
+
params: [schema],
|
|
62
|
+
});
|
|
63
|
+
// Q6: Total table count
|
|
64
|
+
queries.push({
|
|
65
|
+
sql: `SELECT COUNT(*)::int AS count FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE'`,
|
|
66
|
+
params: [schema],
|
|
67
|
+
});
|
|
68
|
+
// Q7: Health — tables without PK (Postgres only)
|
|
69
|
+
queries.push(isPg
|
|
70
|
+
? {
|
|
71
|
+
sql: `SELECT c.relname FROM pg_class c
|
|
72
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
73
|
+
WHERE n.nspname = $1 AND c.relkind = 'r'
|
|
74
|
+
AND NOT EXISTS (SELECT 1 FROM pg_constraint con WHERE con.conrelid = c.oid AND con.contype = 'p')
|
|
75
|
+
ORDER BY c.relname`,
|
|
76
|
+
params: [schema],
|
|
77
|
+
}
|
|
78
|
+
: { sql: `SELECT null AS relname WHERE false` });
|
|
79
|
+
// Q8: Health — RLS disabled count (Postgres only)
|
|
80
|
+
queries.push(isPg
|
|
81
|
+
? {
|
|
82
|
+
sql: `SELECT COUNT(*)::int AS count FROM pg_class c
|
|
83
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
84
|
+
WHERE n.nspname = $1 AND c.relkind = 'r' AND NOT c.relrowsecurity`,
|
|
85
|
+
params: [schema],
|
|
86
|
+
}
|
|
87
|
+
: { sql: `SELECT 0 AS count` });
|
|
88
|
+
const results = await readOnlyMultiQuery(queries);
|
|
89
|
+
// Parse results
|
|
90
|
+
const tables = results[0].rows.map((r) => ({
|
|
91
|
+
name: r.name,
|
|
92
|
+
type: r.type,
|
|
93
|
+
rows: r.rows != null ? Number(r.rows) : null,
|
|
94
|
+
size: r.size_bytes != null ? formatBytes(Number(r.size_bytes)) : null,
|
|
95
|
+
}));
|
|
96
|
+
const views = results[1].rows.map((r) => r.table_name);
|
|
97
|
+
const matviews = results[2].rows.map((r) => r.matviewname);
|
|
98
|
+
const enumTypes = results[3].rows.map((r) => ({
|
|
99
|
+
name: r.name,
|
|
100
|
+
values: r.values,
|
|
101
|
+
}));
|
|
102
|
+
const extensions = results[4].rows.map((r) => r.extname);
|
|
103
|
+
const fnCount = results[5].rows[0]?.count ?? 0;
|
|
104
|
+
const totalTables = results[6].rows[0]?.count ?? 0;
|
|
105
|
+
// Health warnings
|
|
106
|
+
const warnings = [];
|
|
107
|
+
if (isPg) {
|
|
108
|
+
const noPkTables = results[7].rows.map((r) => r.relname);
|
|
109
|
+
if (noPkTables.length > 0) {
|
|
110
|
+
warnings.push(`Tables without primary key: ${noPkTables.join(", ")}`);
|
|
111
|
+
}
|
|
112
|
+
const noRlsCount = results[8].rows[0]?.count ?? 0;
|
|
113
|
+
if (noRlsCount > 0) {
|
|
114
|
+
warnings.push(`Tables with RLS disabled: ${noRlsCount} of ${totalTables}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
schema,
|
|
119
|
+
tables,
|
|
120
|
+
views,
|
|
121
|
+
materialized_views: matviews,
|
|
122
|
+
enum_types: enumTypes,
|
|
123
|
+
extensions,
|
|
124
|
+
counts: {
|
|
125
|
+
tables: totalTables,
|
|
126
|
+
views: views.length,
|
|
127
|
+
materialized_views: matviews.length,
|
|
128
|
+
functions: fnCount,
|
|
129
|
+
enum_types: enumTypes.length,
|
|
130
|
+
...(totalTables > 100 ? { truncated: true } : {}),
|
|
131
|
+
},
|
|
132
|
+
health_warnings: warnings,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function formatBytes(bytes) {
|
|
136
|
+
if (bytes <= 0)
|
|
137
|
+
return "0 B";
|
|
138
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
139
|
+
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
|
140
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=overview.js.map
|