@neverinfamous/postgres-mcp 1.2.0 → 2.0.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/README.md +202 -148
- package/dist/__tests__/benchmarks/codemode.bench.d.ts +10 -0
- package/dist/__tests__/benchmarks/codemode.bench.d.ts.map +1 -0
- package/dist/__tests__/benchmarks/codemode.bench.js +159 -0
- package/dist/__tests__/benchmarks/codemode.bench.js.map +1 -0
- package/dist/__tests__/benchmarks/connection-pool.bench.d.ts +10 -0
- package/dist/__tests__/benchmarks/connection-pool.bench.d.ts.map +1 -0
- package/dist/__tests__/benchmarks/connection-pool.bench.js +123 -0
- package/dist/__tests__/benchmarks/connection-pool.bench.js.map +1 -0
- package/dist/__tests__/benchmarks/handler-dispatch.bench.d.ts +11 -0
- package/dist/__tests__/benchmarks/handler-dispatch.bench.d.ts.map +1 -0
- package/dist/__tests__/benchmarks/handler-dispatch.bench.js +199 -0
- package/dist/__tests__/benchmarks/handler-dispatch.bench.js.map +1 -0
- package/dist/__tests__/benchmarks/logger-sanitization.bench.d.ts +15 -0
- package/dist/__tests__/benchmarks/logger-sanitization.bench.d.ts.map +1 -0
- package/dist/__tests__/benchmarks/logger-sanitization.bench.js +155 -0
- package/dist/__tests__/benchmarks/logger-sanitization.bench.js.map +1 -0
- package/dist/__tests__/benchmarks/resource-prompts.bench.d.ts +10 -0
- package/dist/__tests__/benchmarks/resource-prompts.bench.d.ts.map +1 -0
- package/dist/__tests__/benchmarks/resource-prompts.bench.js +181 -0
- package/dist/__tests__/benchmarks/resource-prompts.bench.js.map +1 -0
- package/dist/__tests__/benchmarks/schema-parsing.bench.d.ts +11 -0
- package/dist/__tests__/benchmarks/schema-parsing.bench.d.ts.map +1 -0
- package/dist/__tests__/benchmarks/schema-parsing.bench.js +209 -0
- package/dist/__tests__/benchmarks/schema-parsing.bench.js.map +1 -0
- package/dist/__tests__/benchmarks/tool-filtering.bench.d.ts +9 -0
- package/dist/__tests__/benchmarks/tool-filtering.bench.d.ts.map +1 -0
- package/dist/__tests__/benchmarks/tool-filtering.bench.js +83 -0
- package/dist/__tests__/benchmarks/tool-filtering.bench.js.map +1 -0
- package/dist/__tests__/benchmarks/transport-auth.bench.d.ts +10 -0
- package/dist/__tests__/benchmarks/transport-auth.bench.d.ts.map +1 -0
- package/dist/__tests__/benchmarks/transport-auth.bench.js +128 -0
- package/dist/__tests__/benchmarks/transport-auth.bench.js.map +1 -0
- package/dist/__tests__/benchmarks/utilities.bench.d.ts +10 -0
- package/dist/__tests__/benchmarks/utilities.bench.d.ts.map +1 -0
- package/dist/__tests__/benchmarks/utilities.bench.js +164 -0
- package/dist/__tests__/benchmarks/utilities.bench.js.map +1 -0
- package/dist/adapters/DatabaseAdapter.d.ts.map +1 -1
- package/dist/adapters/DatabaseAdapter.js +12 -0
- package/dist/adapters/DatabaseAdapter.js.map +1 -1
- package/dist/adapters/postgresql/PostgresAdapter.d.ts.map +1 -1
- package/dist/adapters/postgresql/PostgresAdapter.js +56 -3
- package/dist/adapters/postgresql/PostgresAdapter.js.map +1 -1
- package/dist/adapters/postgresql/prompts/ltree.js +2 -2
- package/dist/adapters/postgresql/prompts/ltree.js.map +1 -1
- package/dist/adapters/postgresql/schemas/admin.d.ts +10 -5
- package/dist/adapters/postgresql/schemas/admin.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/admin.js +10 -5
- package/dist/adapters/postgresql/schemas/admin.js.map +1 -1
- package/dist/adapters/postgresql/schemas/backup.d.ts +45 -27
- package/dist/adapters/postgresql/schemas/backup.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/backup.js +64 -26
- package/dist/adapters/postgresql/schemas/backup.js.map +1 -1
- package/dist/adapters/postgresql/schemas/core.d.ts +53 -19
- package/dist/adapters/postgresql/schemas/core.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/core.js +61 -17
- package/dist/adapters/postgresql/schemas/core.js.map +1 -1
- package/dist/adapters/postgresql/schemas/cron.d.ts +51 -32
- package/dist/adapters/postgresql/schemas/cron.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/cron.js +64 -44
- package/dist/adapters/postgresql/schemas/cron.js.map +1 -1
- package/dist/adapters/postgresql/schemas/extensions.d.ts +224 -110
- package/dist/adapters/postgresql/schemas/extensions.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/extensions.js +245 -96
- package/dist/adapters/postgresql/schemas/extensions.js.map +1 -1
- package/dist/adapters/postgresql/schemas/index.d.ts +7 -6
- package/dist/adapters/postgresql/schemas/index.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/index.js +16 -8
- package/dist/adapters/postgresql/schemas/index.js.map +1 -1
- package/dist/adapters/postgresql/schemas/introspection.d.ts +445 -0
- package/dist/adapters/postgresql/schemas/introspection.d.ts.map +1 -0
- package/dist/adapters/postgresql/schemas/introspection.js +478 -0
- package/dist/adapters/postgresql/schemas/introspection.js.map +1 -0
- package/dist/adapters/postgresql/schemas/jsonb.d.ts +102 -42
- package/dist/adapters/postgresql/schemas/jsonb.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/jsonb.js +125 -30
- package/dist/adapters/postgresql/schemas/jsonb.js.map +1 -1
- package/dist/adapters/postgresql/schemas/monitoring.d.ts +69 -36
- package/dist/adapters/postgresql/schemas/monitoring.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/monitoring.js +98 -40
- package/dist/adapters/postgresql/schemas/monitoring.js.map +1 -1
- package/dist/adapters/postgresql/schemas/partitioning.d.ts +21 -24
- package/dist/adapters/postgresql/schemas/partitioning.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/partitioning.js +26 -14
- package/dist/adapters/postgresql/schemas/partitioning.js.map +1 -1
- package/dist/adapters/postgresql/schemas/partman.d.ts +69 -0
- package/dist/adapters/postgresql/schemas/partman.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/partman.js +46 -33
- package/dist/adapters/postgresql/schemas/partman.js.map +1 -1
- package/dist/adapters/postgresql/schemas/performance.d.ts +97 -49
- package/dist/adapters/postgresql/schemas/performance.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/performance.js +139 -34
- package/dist/adapters/postgresql/schemas/performance.js.map +1 -1
- package/dist/adapters/postgresql/schemas/postgis.d.ts +20 -0
- package/dist/adapters/postgresql/schemas/postgis.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/postgis.js +40 -0
- package/dist/adapters/postgresql/schemas/postgis.js.map +1 -1
- package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts +50 -30
- package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/schema-mgmt.js +105 -33
- package/dist/adapters/postgresql/schemas/schema-mgmt.js.map +1 -1
- package/dist/adapters/postgresql/schemas/stats.d.ts +33 -20
- package/dist/adapters/postgresql/schemas/stats.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/stats.js +36 -20
- package/dist/adapters/postgresql/schemas/stats.js.map +1 -1
- package/dist/adapters/postgresql/schemas/text-search.d.ts +34 -19
- package/dist/adapters/postgresql/schemas/text-search.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/text-search.js +52 -13
- package/dist/adapters/postgresql/schemas/text-search.js.map +1 -1
- package/dist/adapters/postgresql/tools/admin.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/admin.js +272 -186
- package/dist/adapters/postgresql/tools/admin.js.map +1 -1
- package/dist/adapters/postgresql/tools/backup/dump.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/backup/dump.js +376 -350
- package/dist/adapters/postgresql/tools/backup/dump.js.map +1 -1
- package/dist/adapters/postgresql/tools/citext.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/citext.js +333 -243
- package/dist/adapters/postgresql/tools/citext.js.map +1 -1
- package/dist/adapters/postgresql/tools/codemode/index.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/codemode/index.js +2 -11
- package/dist/adapters/postgresql/tools/codemode/index.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/convenience.d.ts +9 -1
- package/dist/adapters/postgresql/tools/core/convenience.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/convenience.js +101 -19
- package/dist/adapters/postgresql/tools/core/convenience.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/error-helpers.d.ts +48 -0
- package/dist/adapters/postgresql/tools/core/error-helpers.d.ts.map +1 -0
- package/dist/adapters/postgresql/tools/core/error-helpers.js +256 -0
- package/dist/adapters/postgresql/tools/core/error-helpers.js.map +1 -0
- package/dist/adapters/postgresql/tools/core/health.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/health.js +18 -4
- package/dist/adapters/postgresql/tools/core/health.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/indexes.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/indexes.js +48 -6
- package/dist/adapters/postgresql/tools/core/indexes.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/objects.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/objects.js +104 -85
- package/dist/adapters/postgresql/tools/core/objects.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/query.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/query.js +100 -42
- package/dist/adapters/postgresql/tools/core/query.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/schemas.d.ts +51 -25
- package/dist/adapters/postgresql/tools/core/schemas.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/schemas.js +51 -25
- package/dist/adapters/postgresql/tools/core/schemas.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/tables.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/tables.js +72 -32
- package/dist/adapters/postgresql/tools/core/tables.js.map +1 -1
- package/dist/adapters/postgresql/tools/cron.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/cron.js +333 -206
- package/dist/adapters/postgresql/tools/cron.js.map +1 -1
- package/dist/adapters/postgresql/tools/introspection.d.ts +15 -0
- package/dist/adapters/postgresql/tools/introspection.d.ts.map +1 -0
- package/dist/adapters/postgresql/tools/introspection.js +1682 -0
- package/dist/adapters/postgresql/tools/introspection.js.map +1 -0
- package/dist/adapters/postgresql/tools/jsonb/advanced.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/jsonb/advanced.js +394 -297
- package/dist/adapters/postgresql/tools/jsonb/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/jsonb/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/jsonb/basic.js +686 -398
- package/dist/adapters/postgresql/tools/jsonb/basic.js.map +1 -1
- package/dist/adapters/postgresql/tools/kcache.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/kcache.js +278 -246
- package/dist/adapters/postgresql/tools/kcache.js.map +1 -1
- package/dist/adapters/postgresql/tools/ltree.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/ltree.js +137 -38
- package/dist/adapters/postgresql/tools/ltree.js.map +1 -1
- package/dist/adapters/postgresql/tools/monitoring.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/monitoring.js +86 -55
- package/dist/adapters/postgresql/tools/monitoring.js.map +1 -1
- package/dist/adapters/postgresql/tools/partitioning.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/partitioning.js +79 -15
- package/dist/adapters/postgresql/tools/partitioning.js.map +1 -1
- package/dist/adapters/postgresql/tools/partman/management.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/partman/management.js +43 -56
- package/dist/adapters/postgresql/tools/partman/management.js.map +1 -1
- package/dist/adapters/postgresql/tools/partman/operations.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/partman/operations.js +137 -24
- package/dist/adapters/postgresql/tools/partman/operations.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/analysis.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/analysis.js +276 -165
- package/dist/adapters/postgresql/tools/performance/analysis.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/explain.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/explain.js +61 -21
- package/dist/adapters/postgresql/tools/performance/explain.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/monitoring.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/monitoring.js +52 -12
- package/dist/adapters/postgresql/tools/performance/monitoring.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/optimization.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/optimization.js +92 -81
- package/dist/adapters/postgresql/tools/performance/optimization.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/stats.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/stats.js +182 -60
- package/dist/adapters/postgresql/tools/performance/stats.js.map +1 -1
- package/dist/adapters/postgresql/tools/pgcrypto.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/pgcrypto.js +277 -102
- package/dist/adapters/postgresql/tools/pgcrypto.js.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/advanced.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/advanced.js +298 -230
- package/dist/adapters/postgresql/tools/postgis/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/basic.js +370 -251
- package/dist/adapters/postgresql/tools/postgis/basic.js.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/standalone.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/standalone.js +135 -51
- package/dist/adapters/postgresql/tools/postgis/standalone.js.map +1 -1
- package/dist/adapters/postgresql/tools/schema.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/schema.js +580 -233
- package/dist/adapters/postgresql/tools/schema.js.map +1 -1
- package/dist/adapters/postgresql/tools/stats/advanced.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/stats/advanced.js +567 -506
- package/dist/adapters/postgresql/tools/stats/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/stats/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/stats/basic.js +340 -316
- package/dist/adapters/postgresql/tools/stats/basic.js.map +1 -1
- package/dist/adapters/postgresql/tools/text.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/text.js +690 -337
- package/dist/adapters/postgresql/tools/text.js.map +1 -1
- package/dist/adapters/postgresql/tools/transactions.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/transactions.js +157 -50
- package/dist/adapters/postgresql/tools/transactions.js.map +1 -1
- package/dist/adapters/postgresql/tools/vector/advanced.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/vector/advanced.js +18 -0
- package/dist/adapters/postgresql/tools/vector/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/vector/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/vector/basic.js +100 -53
- package/dist/adapters/postgresql/tools/vector/basic.js.map +1 -1
- package/dist/auth/auth-context.d.ts +28 -0
- package/dist/auth/auth-context.d.ts.map +1 -0
- package/dist/auth/auth-context.js +37 -0
- package/dist/auth/auth-context.js.map +1 -0
- package/dist/auth/scope-map.d.ts +20 -0
- package/dist/auth/scope-map.d.ts.map +1 -0
- package/dist/auth/scope-map.js +40 -0
- package/dist/auth/scope-map.js.map +1 -0
- package/dist/auth/scopes.d.ts.map +1 -1
- package/dist/auth/scopes.js +2 -0
- package/dist/auth/scopes.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/codemode/api.d.ts +1 -0
- package/dist/codemode/api.d.ts.map +1 -1
- package/dist/codemode/api.js +35 -1
- package/dist/codemode/api.js.map +1 -1
- package/dist/codemode/index.d.ts +0 -2
- package/dist/codemode/index.d.ts.map +1 -1
- package/dist/codemode/index.js +0 -4
- package/dist/codemode/index.js.map +1 -1
- package/dist/codemode/sandbox.d.ts +14 -1
- package/dist/codemode/sandbox.d.ts.map +1 -1
- package/dist/codemode/sandbox.js +58 -19
- package/dist/codemode/sandbox.js.map +1 -1
- package/dist/codemode/types.d.ts.map +1 -1
- package/dist/codemode/types.js +3 -0
- package/dist/codemode/types.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +5 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +117 -31
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/filtering/ToolConstants.d.ts +22 -19
- package/dist/filtering/ToolConstants.d.ts.map +1 -1
- package/dist/filtering/ToolConstants.js +48 -37
- package/dist/filtering/ToolConstants.js.map +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +10 -13
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/pool/ConnectionPool.js +1 -1
- package/dist/pool/ConnectionPool.js.map +1 -1
- package/dist/transports/http.d.ts +1 -0
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +75 -21
- package/dist/transports/http.js.map +1 -1
- package/dist/types/filtering.d.ts +2 -2
- package/dist/types/filtering.d.ts.map +1 -1
- package/dist/utils/icons.d.ts.map +1 -1
- package/dist/utils/icons.js +5 -0
- package/dist/utils/icons.js.map +1 -1
- package/dist/utils/where-clause.d.ts.map +1 -1
- package/dist/utils/where-clause.js +24 -0
- package/dist/utils/where-clause.js.map +1 -1
- package/package.json +20 -13
- package/dist/codemode/sandbox-factory.d.ts +0 -72
- package/dist/codemode/sandbox-factory.d.ts.map +0 -1
- package/dist/codemode/sandbox-factory.js +0 -88
- package/dist/codemode/sandbox-factory.js.map +0 -1
- package/dist/codemode/worker-sandbox.d.ts +0 -82
- package/dist/codemode/worker-sandbox.d.ts.map +0 -1
- package/dist/codemode/worker-sandbox.js +0 -244
- package/dist/codemode/worker-sandbox.js.map +0 -1
- package/dist/codemode/worker-script.d.ts +0 -8
- package/dist/codemode/worker-script.d.ts.map +0 -1
- package/dist/codemode/worker-script.js +0 -113
- package/dist/codemode/worker-script.js.map +0 -1
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { readOnly } from "../../../../utils/annotations.js";
|
|
7
7
|
import { getToolIcons } from "../../../../utils/icons.js";
|
|
8
|
+
import { formatPostgresError } from "../core/error-helpers.js";
|
|
9
|
+
import { sanitizeWhereClause } from "../../../../utils/where-clause.js";
|
|
8
10
|
import {
|
|
9
11
|
// Base schemas for MCP visibility
|
|
10
12
|
StatsDescriptiveSchemaBase, StatsPercentilesSchemaBase, StatsCorrelationSchemaBase, StatsRegressionSchemaBase,
|
|
@@ -28,61 +30,69 @@ export function createStatsDescriptiveTool(adapter) {
|
|
|
28
30
|
annotations: readOnly("Descriptive Statistics"),
|
|
29
31
|
icons: getToolIcons("stats", readOnly("Descriptive Statistics")),
|
|
30
32
|
handler: async (params, _context) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
AND
|
|
33
|
+
try {
|
|
34
|
+
const { table, column, schema, where, params: queryParams, groupBy, } = StatsDescriptiveSchema.parse(params);
|
|
35
|
+
const schemaPrefix = schema ? `"${schema}".` : "";
|
|
36
|
+
const whereClause = where ? `WHERE ${sanitizeWhereClause(where)}` : "";
|
|
37
|
+
// Validate column is numeric type
|
|
38
|
+
const typeCheckQuery = `
|
|
39
|
+
SELECT data_type
|
|
40
|
+
FROM information_schema.columns
|
|
41
|
+
WHERE table_schema = $1
|
|
42
|
+
AND table_name = $2
|
|
43
|
+
AND column_name = $3
|
|
41
44
|
`;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
const typeResult = await adapter.executeQuery(typeCheckQuery, [
|
|
46
|
+
schema ?? "public",
|
|
47
|
+
table,
|
|
48
|
+
column,
|
|
49
|
+
]);
|
|
50
|
+
const typeRow = typeResult.rows?.[0];
|
|
51
|
+
if (!typeRow) {
|
|
52
|
+
// Check if table exists
|
|
53
|
+
const tableCheckQuery = `
|
|
54
|
+
SELECT 1 FROM information_schema.tables
|
|
55
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
49
56
|
`;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
const tableResult = await adapter.executeQuery(tableCheckQuery, [
|
|
58
|
+
schema ?? "public",
|
|
59
|
+
table,
|
|
60
|
+
]);
|
|
61
|
+
if (tableResult.rows?.length === 0) {
|
|
62
|
+
throw new Error(`Table "${schema ?? "public"}.${table}" not found`);
|
|
63
|
+
}
|
|
64
|
+
throw new Error(`Column "${column}" not found in table "${schema ?? "public"}.${table}"`);
|
|
53
65
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const sql = `
|
|
85
|
-
SELECT
|
|
66
|
+
const numericTypes = [
|
|
67
|
+
"integer",
|
|
68
|
+
"bigint",
|
|
69
|
+
"smallint",
|
|
70
|
+
"numeric",
|
|
71
|
+
"decimal",
|
|
72
|
+
"real",
|
|
73
|
+
"double precision",
|
|
74
|
+
"money",
|
|
75
|
+
];
|
|
76
|
+
if (!numericTypes.includes(typeRow.data_type)) {
|
|
77
|
+
throw new Error(`Column "${column}" is type "${typeRow.data_type}" but must be a numeric type for statistical analysis`);
|
|
78
|
+
}
|
|
79
|
+
// Helper to map stats row to numeric object
|
|
80
|
+
const mapStats = (row) => ({
|
|
81
|
+
count: Number(row["count"]),
|
|
82
|
+
min: row["min"] !== null ? Number(row["min"]) : null,
|
|
83
|
+
max: row["max"] !== null ? Number(row["max"]) : null,
|
|
84
|
+
avg: row["avg"] !== null ? Number(row["avg"]) : null,
|
|
85
|
+
stddev: row["stddev"] !== null ? Number(row["stddev"]) : null,
|
|
86
|
+
variance: row["variance"] !== null ? Number(row["variance"]) : null,
|
|
87
|
+
sum: row["sum"] !== null ? Number(row["sum"]) : null,
|
|
88
|
+
mode: row["mode"] !== null && row["mode"] !== undefined
|
|
89
|
+
? Number(row["mode"])
|
|
90
|
+
: null,
|
|
91
|
+
});
|
|
92
|
+
if (groupBy !== undefined) {
|
|
93
|
+
// Grouped statistics
|
|
94
|
+
const sql = `
|
|
95
|
+
SELECT
|
|
86
96
|
"${groupBy}" as group_key,
|
|
87
97
|
COUNT("${column}") as count,
|
|
88
98
|
MIN("${column}") as min,
|
|
@@ -97,25 +107,25 @@ export function createStatsDescriptiveTool(adapter) {
|
|
|
97
107
|
GROUP BY "${groupBy}"
|
|
98
108
|
ORDER BY "${groupBy}"
|
|
99
109
|
`;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
SELECT
|
|
110
|
+
const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
|
|
111
|
+
? [queryParams]
|
|
112
|
+
: []));
|
|
113
|
+
const rows = result.rows ?? [];
|
|
114
|
+
const groups = rows.map((row) => ({
|
|
115
|
+
groupKey: row["group_key"],
|
|
116
|
+
statistics: mapStats(row),
|
|
117
|
+
}));
|
|
118
|
+
return {
|
|
119
|
+
table: `${schema ?? "public"}.${table}`,
|
|
120
|
+
column,
|
|
121
|
+
groupBy,
|
|
122
|
+
groups,
|
|
123
|
+
count: groups.length,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Ungrouped statistics (original behavior)
|
|
127
|
+
const sql = `
|
|
128
|
+
SELECT
|
|
119
129
|
COUNT("${column}") as count,
|
|
120
130
|
MIN("${column}") as min,
|
|
121
131
|
MAX("${column}") as max,
|
|
@@ -127,17 +137,24 @@ export function createStatsDescriptiveTool(adapter) {
|
|
|
127
137
|
FROM ${schemaPrefix}"${table}"
|
|
128
138
|
${whereClause}
|
|
129
139
|
`;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
|
|
141
|
+
? [queryParams]
|
|
142
|
+
: []));
|
|
143
|
+
const stats = result.rows?.[0];
|
|
144
|
+
if (!stats)
|
|
145
|
+
throw new Error("No stats found");
|
|
146
|
+
return {
|
|
147
|
+
table: `${schema ?? "public"}.${table}`,
|
|
148
|
+
column,
|
|
149
|
+
statistics: mapStats(stats),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
error: formatPostgresError(error, { tool: "pg_stats_descriptive" }),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
141
158
|
},
|
|
142
159
|
};
|
|
143
160
|
}
|
|
@@ -157,21 +174,28 @@ async function validateNumericColumn(adapter, table, column, schema) {
|
|
|
157
174
|
"money",
|
|
158
175
|
];
|
|
159
176
|
const typeCheckQuery = `
|
|
160
|
-
SELECT data_type
|
|
161
|
-
FROM information_schema.columns
|
|
162
|
-
WHERE table_schema =
|
|
163
|
-
AND table_name =
|
|
164
|
-
AND column_name =
|
|
177
|
+
SELECT data_type
|
|
178
|
+
FROM information_schema.columns
|
|
179
|
+
WHERE table_schema = $1
|
|
180
|
+
AND table_name = $2
|
|
181
|
+
AND column_name = $3
|
|
165
182
|
`;
|
|
166
|
-
const typeResult = await adapter.executeQuery(typeCheckQuery
|
|
183
|
+
const typeResult = await adapter.executeQuery(typeCheckQuery, [
|
|
184
|
+
schema,
|
|
185
|
+
table,
|
|
186
|
+
column,
|
|
187
|
+
]);
|
|
167
188
|
const typeRow = typeResult.rows?.[0];
|
|
168
189
|
if (!typeRow) {
|
|
169
190
|
// Check if table exists
|
|
170
191
|
const tableCheckQuery = `
|
|
171
|
-
SELECT 1 FROM information_schema.tables
|
|
172
|
-
WHERE table_schema =
|
|
192
|
+
SELECT 1 FROM information_schema.tables
|
|
193
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
173
194
|
`;
|
|
174
|
-
const tableResult = await adapter.executeQuery(tableCheckQuery
|
|
195
|
+
const tableResult = await adapter.executeQuery(tableCheckQuery, [
|
|
196
|
+
schema,
|
|
197
|
+
table,
|
|
198
|
+
]);
|
|
175
199
|
if (tableResult.rows?.length === 0) {
|
|
176
200
|
throw new Error(`Table "${schema}.${table}" not found`);
|
|
177
201
|
}
|
|
@@ -194,33 +218,34 @@ export function createStatsPercentilesTool(adapter) {
|
|
|
194
218
|
annotations: readOnly("Percentiles"),
|
|
195
219
|
icons: getToolIcons("stats", readOnly("Percentiles")),
|
|
196
220
|
handler: async (params, _context) => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
try {
|
|
222
|
+
const parsed = StatsPercentilesSchema.parse(params);
|
|
223
|
+
const { table, column, percentiles, schema, where, params: queryParams, groupBy, _percentileScaleWarning, } = parsed;
|
|
224
|
+
const schemaName = schema ?? "public";
|
|
225
|
+
// Validate column exists and is numeric
|
|
226
|
+
await validateNumericColumn(adapter, table, column, schemaName);
|
|
227
|
+
const pctiles = percentiles ?? [0.25, 0.5, 0.75];
|
|
228
|
+
const schemaPrefix = schema ? `"${schema}".` : "";
|
|
229
|
+
const whereClause = where ? `WHERE ${sanitizeWhereClause(where)}` : "";
|
|
230
|
+
const percentileSelects = pctiles
|
|
231
|
+
.map((p) => `PERCENTILE_CONT(${String(p)}) WITHIN GROUP (ORDER BY "${column}") as p${String(Math.round(p * 100))}`)
|
|
232
|
+
.join(",\n ");
|
|
233
|
+
// Helper to map row to percentile results (round to 6 decimal places to avoid floating-point artifacts)
|
|
234
|
+
const mapPercentiles = (row) => {
|
|
235
|
+
const result = {};
|
|
236
|
+
for (const p of pctiles) {
|
|
237
|
+
const key = `p${String(Math.round(p * 100))}`;
|
|
238
|
+
const val = row[key] !== null && row[key] !== undefined
|
|
239
|
+
? Number(row[key])
|
|
240
|
+
: null;
|
|
241
|
+
result[key] = val !== null ? Math.round(val * 1e6) / 1e6 : null;
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
};
|
|
245
|
+
if (groupBy !== undefined) {
|
|
246
|
+
// Grouped percentiles
|
|
247
|
+
const sql = `
|
|
248
|
+
SELECT
|
|
224
249
|
"${groupBy}" as group_key,
|
|
225
250
|
${percentileSelects}
|
|
226
251
|
FROM ${schemaPrefix}"${table}"
|
|
@@ -228,20 +253,42 @@ export function createStatsPercentilesTool(adapter) {
|
|
|
228
253
|
GROUP BY "${groupBy}"
|
|
229
254
|
ORDER BY "${groupBy}"
|
|
230
255
|
`;
|
|
256
|
+
const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
|
|
257
|
+
? [queryParams]
|
|
258
|
+
: []));
|
|
259
|
+
const rows = result.rows ?? [];
|
|
260
|
+
const groups = rows.map((row) => ({
|
|
261
|
+
groupKey: row["group_key"],
|
|
262
|
+
percentiles: mapPercentiles(row),
|
|
263
|
+
}));
|
|
264
|
+
const response = {
|
|
265
|
+
table: `${schema ?? "public"}.${table}`,
|
|
266
|
+
column,
|
|
267
|
+
groupBy,
|
|
268
|
+
groups,
|
|
269
|
+
count: groups.length,
|
|
270
|
+
};
|
|
271
|
+
// Include warning if mixed scales were detected
|
|
272
|
+
if (_percentileScaleWarning) {
|
|
273
|
+
response["warning"] = _percentileScaleWarning;
|
|
274
|
+
}
|
|
275
|
+
return response;
|
|
276
|
+
}
|
|
277
|
+
// Ungrouped percentiles
|
|
278
|
+
const sql = `
|
|
279
|
+
SELECT
|
|
280
|
+
${percentileSelects}
|
|
281
|
+
FROM ${schemaPrefix}"${table}"
|
|
282
|
+
${whereClause}
|
|
283
|
+
`;
|
|
231
284
|
const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
|
|
232
285
|
? [queryParams]
|
|
233
286
|
: []));
|
|
234
|
-
const
|
|
235
|
-
const groups = rows.map((row) => ({
|
|
236
|
-
groupKey: row["group_key"],
|
|
237
|
-
percentiles: mapPercentiles(row),
|
|
238
|
-
}));
|
|
287
|
+
const row = result.rows?.[0] ?? {};
|
|
239
288
|
const response = {
|
|
240
289
|
table: `${schema ?? "public"}.${table}`,
|
|
241
290
|
column,
|
|
242
|
-
|
|
243
|
-
groups,
|
|
244
|
-
count: groups.length,
|
|
291
|
+
percentiles: mapPercentiles(row),
|
|
245
292
|
};
|
|
246
293
|
// Include warning if mixed scales were detected
|
|
247
294
|
if (_percentileScaleWarning) {
|
|
@@ -249,27 +296,12 @@ export function createStatsPercentilesTool(adapter) {
|
|
|
249
296
|
}
|
|
250
297
|
return response;
|
|
251
298
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
${whereClause}
|
|
258
|
-
`;
|
|
259
|
-
const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
|
|
260
|
-
? [queryParams]
|
|
261
|
-
: []));
|
|
262
|
-
const row = result.rows?.[0] ?? {};
|
|
263
|
-
const response = {
|
|
264
|
-
table: `${schema ?? "public"}.${table}`,
|
|
265
|
-
column,
|
|
266
|
-
percentiles: mapPercentiles(row),
|
|
267
|
-
};
|
|
268
|
-
// Include warning if mixed scales were detected
|
|
269
|
-
if (_percentileScaleWarning) {
|
|
270
|
-
response["warning"] = _percentileScaleWarning;
|
|
299
|
+
catch (error) {
|
|
300
|
+
return {
|
|
301
|
+
success: false,
|
|
302
|
+
error: formatPostgresError(error, { tool: "pg_stats_percentiles" }),
|
|
303
|
+
};
|
|
271
304
|
}
|
|
272
|
-
return response;
|
|
273
305
|
},
|
|
274
306
|
};
|
|
275
307
|
}
|
|
@@ -286,76 +318,53 @@ export function createStatsCorrelationTool(adapter) {
|
|
|
286
318
|
annotations: readOnly("Correlation Analysis"),
|
|
287
319
|
icons: getToolIcons("stats", readOnly("Correlation Analysis")),
|
|
288
320
|
handler: async (params, _context) => {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
if (!numericTypes.includes(typeRow.data_type)) {
|
|
318
|
-
throw new Error(`Column "${col}" is type "${typeRow.data_type}" but must be numeric for correlation analysis`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
// Helper to interpret correlation
|
|
322
|
-
const interpretCorr = (corr) => {
|
|
323
|
-
if (corr === null)
|
|
324
|
-
return "N/A";
|
|
325
|
-
const absCorr = Math.abs(corr);
|
|
326
|
-
let interpretation;
|
|
327
|
-
if (absCorr >= 0.9)
|
|
328
|
-
interpretation = "Very strong";
|
|
329
|
-
else if (absCorr >= 0.7)
|
|
330
|
-
interpretation = "Strong";
|
|
331
|
-
else if (absCorr >= 0.5)
|
|
332
|
-
interpretation = "Moderate";
|
|
333
|
-
else if (absCorr >= 0.3)
|
|
334
|
-
interpretation = "Weak";
|
|
335
|
-
else
|
|
336
|
-
interpretation = "Very weak or no correlation";
|
|
337
|
-
interpretation += corr < 0 ? " (negative)" : " (positive)";
|
|
338
|
-
return interpretation;
|
|
339
|
-
};
|
|
340
|
-
// Helper to map row to correlation result
|
|
341
|
-
const mapCorrelation = (row) => {
|
|
342
|
-
const corr = row["correlation"] !== null ? Number(row["correlation"]) : null;
|
|
343
|
-
return {
|
|
344
|
-
correlation: corr,
|
|
345
|
-
interpretation: interpretCorr(corr),
|
|
346
|
-
covariancePopulation: row["covariance_pop"] !== null
|
|
347
|
-
? Number(row["covariance_pop"])
|
|
348
|
-
: null,
|
|
349
|
-
covarianceSample: row["covariance_sample"] !== null
|
|
350
|
-
? Number(row["covariance_sample"])
|
|
351
|
-
: null,
|
|
352
|
-
sampleSize: Number(row["sample_size"]),
|
|
321
|
+
try {
|
|
322
|
+
const parsed = StatsCorrelationSchema.parse(params);
|
|
323
|
+
const { table, column1, column2, schema, where, params: queryParams, groupBy, } = parsed;
|
|
324
|
+
const schemaName = schema ?? "public";
|
|
325
|
+
const schemaPrefix = schema ? `"${schema}".` : "";
|
|
326
|
+
const whereClause = where ? `WHERE ${sanitizeWhereClause(where)}` : "";
|
|
327
|
+
// Validate both columns exist and are numeric (with table-first error checking)
|
|
328
|
+
await validateNumericColumn(adapter, table, column1, schemaName);
|
|
329
|
+
await validateNumericColumn(adapter, table, column2, schemaName);
|
|
330
|
+
// Helper to interpret correlation
|
|
331
|
+
const interpretCorr = (corr) => {
|
|
332
|
+
if (corr === null)
|
|
333
|
+
return "N/A";
|
|
334
|
+
const absCorr = Math.abs(corr);
|
|
335
|
+
let interpretation;
|
|
336
|
+
if (absCorr >= 0.9)
|
|
337
|
+
interpretation = "Very strong";
|
|
338
|
+
else if (absCorr >= 0.7)
|
|
339
|
+
interpretation = "Strong";
|
|
340
|
+
else if (absCorr >= 0.5)
|
|
341
|
+
interpretation = "Moderate";
|
|
342
|
+
else if (absCorr >= 0.3)
|
|
343
|
+
interpretation = "Weak";
|
|
344
|
+
else
|
|
345
|
+
interpretation = "Very weak or no correlation";
|
|
346
|
+
interpretation += corr < 0 ? " (negative)" : " (positive)";
|
|
347
|
+
return interpretation;
|
|
353
348
|
};
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
349
|
+
// Helper to map row to correlation result
|
|
350
|
+
const mapCorrelation = (row) => {
|
|
351
|
+
const corr = row["correlation"] !== null ? Number(row["correlation"]) : null;
|
|
352
|
+
return {
|
|
353
|
+
correlation: corr,
|
|
354
|
+
interpretation: interpretCorr(corr),
|
|
355
|
+
covariancePopulation: row["covariance_pop"] !== null
|
|
356
|
+
? Number(row["covariance_pop"])
|
|
357
|
+
: null,
|
|
358
|
+
covarianceSample: row["covariance_sample"] !== null
|
|
359
|
+
? Number(row["covariance_sample"])
|
|
360
|
+
: null,
|
|
361
|
+
sampleSize: Number(row["sample_size"]),
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
if (groupBy !== undefined) {
|
|
365
|
+
// Grouped correlation
|
|
366
|
+
const sql = `
|
|
367
|
+
SELECT
|
|
359
368
|
"${groupBy}" as group_key,
|
|
360
369
|
CORR("${column1}", "${column2}")::numeric(10,6) as correlation,
|
|
361
370
|
COVAR_POP("${column1}", "${column2}")::numeric(20,6) as covariance_pop,
|
|
@@ -366,25 +375,25 @@ export function createStatsCorrelationTool(adapter) {
|
|
|
366
375
|
GROUP BY "${groupBy}"
|
|
367
376
|
ORDER BY "${groupBy}"
|
|
368
377
|
`;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
SELECT
|
|
378
|
+
const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
|
|
379
|
+
? [queryParams]
|
|
380
|
+
: []));
|
|
381
|
+
const rows = result.rows ?? [];
|
|
382
|
+
const groups = rows.map((row) => ({
|
|
383
|
+
groupKey: row["group_key"],
|
|
384
|
+
...mapCorrelation(row),
|
|
385
|
+
}));
|
|
386
|
+
return {
|
|
387
|
+
table: `${schema ?? "public"}.${table}`,
|
|
388
|
+
columns: [column1, column2],
|
|
389
|
+
groupBy,
|
|
390
|
+
groups,
|
|
391
|
+
count: groups.length,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
// Ungrouped correlation
|
|
395
|
+
const sql = `
|
|
396
|
+
SELECT
|
|
388
397
|
CORR("${column1}", "${column2}")::numeric(10,6) as correlation,
|
|
389
398
|
COVAR_POP("${column1}", "${column2}")::numeric(20,6) as covariance_pop,
|
|
390
399
|
COVAR_SAMP("${column1}", "${column2}")::numeric(20,6) as covariance_sample,
|
|
@@ -392,22 +401,29 @@ export function createStatsCorrelationTool(adapter) {
|
|
|
392
401
|
FROM ${schemaPrefix}"${table}"
|
|
393
402
|
${whereClause}
|
|
394
403
|
`;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
404
|
+
const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
|
|
405
|
+
? [queryParams]
|
|
406
|
+
: []));
|
|
407
|
+
const row = result.rows?.[0];
|
|
408
|
+
if (!row)
|
|
409
|
+
throw new Error("No correlation data found");
|
|
410
|
+
const response = {
|
|
411
|
+
table: `${schema ?? "public"}.${table}`,
|
|
412
|
+
columns: [column1, column2],
|
|
413
|
+
...mapCorrelation(row),
|
|
414
|
+
};
|
|
415
|
+
// Add note for self-correlation
|
|
416
|
+
if (column1 === column2) {
|
|
417
|
+
response["note"] = "Self-correlation always equals 1.0";
|
|
418
|
+
}
|
|
419
|
+
return response;
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
return {
|
|
423
|
+
success: false,
|
|
424
|
+
error: formatPostgresError(error, { tool: "pg_stats_correlation" }),
|
|
425
|
+
};
|
|
409
426
|
}
|
|
410
|
-
return response;
|
|
411
427
|
},
|
|
412
428
|
};
|
|
413
429
|
}
|
|
@@ -424,38 +440,39 @@ export function createStatsRegressionTool(adapter) {
|
|
|
424
440
|
annotations: readOnly("Linear Regression"),
|
|
425
441
|
icons: getToolIcons("stats", readOnly("Linear Regression")),
|
|
426
442
|
handler: async (params, _context) => {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
443
|
+
try {
|
|
444
|
+
const parsed = StatsRegressionSchema.parse(params);
|
|
445
|
+
const { table, xColumn, yColumn, schema, where, params: queryParams, groupBy, } = parsed;
|
|
446
|
+
const schemaName = schema ?? "public";
|
|
447
|
+
const schemaPrefix = schema ? `"${schema}".` : "";
|
|
448
|
+
const whereClause = where ? `WHERE ${sanitizeWhereClause(where)}` : "";
|
|
449
|
+
// Validate both columns exist and are numeric
|
|
450
|
+
await validateNumericColumn(adapter, table, xColumn, schemaName);
|
|
451
|
+
await validateNumericColumn(adapter, table, yColumn, schemaName);
|
|
452
|
+
// Helper to map row to regression result
|
|
453
|
+
const mapRegression = (row) => {
|
|
454
|
+
const slope = row["slope"] !== null ? Number(row["slope"]) : null;
|
|
455
|
+
const intercept = row["intercept"] !== null ? Number(row["intercept"]) : null;
|
|
456
|
+
const rSquared = row["r_squared"] !== null ? Number(row["r_squared"]) : null;
|
|
457
|
+
let equation = "N/A";
|
|
458
|
+
if (slope !== null && intercept !== null) {
|
|
459
|
+
const sign = intercept >= 0 ? "+" : "-";
|
|
460
|
+
equation = `y = ${slope.toFixed(4)}x ${sign} ${Math.abs(intercept).toFixed(4)}`;
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
slope,
|
|
464
|
+
intercept,
|
|
465
|
+
rSquared,
|
|
466
|
+
equation,
|
|
467
|
+
avgX: row["avg_x"] !== null ? Number(row["avg_x"]) : null,
|
|
468
|
+
avgY: row["avg_y"] !== null ? Number(row["avg_y"]) : null,
|
|
469
|
+
sampleSize: Number(row["sample_size"]),
|
|
470
|
+
};
|
|
453
471
|
};
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
SELECT
|
|
472
|
+
if (groupBy !== undefined) {
|
|
473
|
+
// Grouped regression
|
|
474
|
+
const sql = `
|
|
475
|
+
SELECT
|
|
459
476
|
"${groupBy}" as group_key,
|
|
460
477
|
REGR_SLOPE("${yColumn}", "${xColumn}")::numeric(20,6) as slope,
|
|
461
478
|
REGR_INTERCEPT("${yColumn}", "${xColumn}")::numeric(20,6) as intercept,
|
|
@@ -468,26 +485,26 @@ export function createStatsRegressionTool(adapter) {
|
|
|
468
485
|
GROUP BY "${groupBy}"
|
|
469
486
|
ORDER BY "${groupBy}"
|
|
470
487
|
`;
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
SELECT
|
|
488
|
+
const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
|
|
489
|
+
? [queryParams]
|
|
490
|
+
: []));
|
|
491
|
+
const rows = result.rows ?? [];
|
|
492
|
+
const groups = rows.map((row) => ({
|
|
493
|
+
groupKey: row["group_key"],
|
|
494
|
+
regression: mapRegression(row),
|
|
495
|
+
}));
|
|
496
|
+
return {
|
|
497
|
+
table: `${schema ?? "public"}.${table}`,
|
|
498
|
+
xColumn,
|
|
499
|
+
yColumn,
|
|
500
|
+
groupBy,
|
|
501
|
+
groups,
|
|
502
|
+
count: groups.length,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
// Ungrouped regression
|
|
506
|
+
const sql = `
|
|
507
|
+
SELECT
|
|
491
508
|
REGR_SLOPE("${yColumn}", "${xColumn}")::numeric(20,6) as slope,
|
|
492
509
|
REGR_INTERCEPT("${yColumn}", "${xColumn}")::numeric(20,6) as intercept,
|
|
493
510
|
REGR_R2("${yColumn}", "${xColumn}")::numeric(10,6) as r_squared,
|
|
@@ -500,23 +517,30 @@ export function createStatsRegressionTool(adapter) {
|
|
|
500
517
|
FROM ${schemaPrefix}"${table}"
|
|
501
518
|
${whereClause}
|
|
502
519
|
`;
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
520
|
+
const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
|
|
521
|
+
? [queryParams]
|
|
522
|
+
: []));
|
|
523
|
+
const row = result.rows?.[0];
|
|
524
|
+
if (!row)
|
|
525
|
+
return { error: "No regression data found" };
|
|
526
|
+
const response = {
|
|
527
|
+
table: `${schema ?? "public"}.${table}`,
|
|
528
|
+
xColumn,
|
|
529
|
+
yColumn,
|
|
530
|
+
regression: mapRegression(row),
|
|
531
|
+
};
|
|
532
|
+
// Add note for self-regression
|
|
533
|
+
if (xColumn === yColumn) {
|
|
534
|
+
response["note"] = "Self-regression always returns slope=1, r²=1";
|
|
535
|
+
}
|
|
536
|
+
return response;
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
return {
|
|
540
|
+
success: false,
|
|
541
|
+
error: formatPostgresError(error, { tool: "pg_stats_regression" }),
|
|
542
|
+
};
|
|
518
543
|
}
|
|
519
|
-
return response;
|
|
520
544
|
},
|
|
521
545
|
};
|
|
522
546
|
}
|