@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.
Files changed (293) hide show
  1. package/README.md +202 -148
  2. package/dist/__tests__/benchmarks/codemode.bench.d.ts +10 -0
  3. package/dist/__tests__/benchmarks/codemode.bench.d.ts.map +1 -0
  4. package/dist/__tests__/benchmarks/codemode.bench.js +159 -0
  5. package/dist/__tests__/benchmarks/codemode.bench.js.map +1 -0
  6. package/dist/__tests__/benchmarks/connection-pool.bench.d.ts +10 -0
  7. package/dist/__tests__/benchmarks/connection-pool.bench.d.ts.map +1 -0
  8. package/dist/__tests__/benchmarks/connection-pool.bench.js +123 -0
  9. package/dist/__tests__/benchmarks/connection-pool.bench.js.map +1 -0
  10. package/dist/__tests__/benchmarks/handler-dispatch.bench.d.ts +11 -0
  11. package/dist/__tests__/benchmarks/handler-dispatch.bench.d.ts.map +1 -0
  12. package/dist/__tests__/benchmarks/handler-dispatch.bench.js +199 -0
  13. package/dist/__tests__/benchmarks/handler-dispatch.bench.js.map +1 -0
  14. package/dist/__tests__/benchmarks/logger-sanitization.bench.d.ts +15 -0
  15. package/dist/__tests__/benchmarks/logger-sanitization.bench.d.ts.map +1 -0
  16. package/dist/__tests__/benchmarks/logger-sanitization.bench.js +155 -0
  17. package/dist/__tests__/benchmarks/logger-sanitization.bench.js.map +1 -0
  18. package/dist/__tests__/benchmarks/resource-prompts.bench.d.ts +10 -0
  19. package/dist/__tests__/benchmarks/resource-prompts.bench.d.ts.map +1 -0
  20. package/dist/__tests__/benchmarks/resource-prompts.bench.js +181 -0
  21. package/dist/__tests__/benchmarks/resource-prompts.bench.js.map +1 -0
  22. package/dist/__tests__/benchmarks/schema-parsing.bench.d.ts +11 -0
  23. package/dist/__tests__/benchmarks/schema-parsing.bench.d.ts.map +1 -0
  24. package/dist/__tests__/benchmarks/schema-parsing.bench.js +209 -0
  25. package/dist/__tests__/benchmarks/schema-parsing.bench.js.map +1 -0
  26. package/dist/__tests__/benchmarks/tool-filtering.bench.d.ts +9 -0
  27. package/dist/__tests__/benchmarks/tool-filtering.bench.d.ts.map +1 -0
  28. package/dist/__tests__/benchmarks/tool-filtering.bench.js +83 -0
  29. package/dist/__tests__/benchmarks/tool-filtering.bench.js.map +1 -0
  30. package/dist/__tests__/benchmarks/transport-auth.bench.d.ts +10 -0
  31. package/dist/__tests__/benchmarks/transport-auth.bench.d.ts.map +1 -0
  32. package/dist/__tests__/benchmarks/transport-auth.bench.js +128 -0
  33. package/dist/__tests__/benchmarks/transport-auth.bench.js.map +1 -0
  34. package/dist/__tests__/benchmarks/utilities.bench.d.ts +10 -0
  35. package/dist/__tests__/benchmarks/utilities.bench.d.ts.map +1 -0
  36. package/dist/__tests__/benchmarks/utilities.bench.js +164 -0
  37. package/dist/__tests__/benchmarks/utilities.bench.js.map +1 -0
  38. package/dist/adapters/DatabaseAdapter.d.ts.map +1 -1
  39. package/dist/adapters/DatabaseAdapter.js +12 -0
  40. package/dist/adapters/DatabaseAdapter.js.map +1 -1
  41. package/dist/adapters/postgresql/PostgresAdapter.d.ts.map +1 -1
  42. package/dist/adapters/postgresql/PostgresAdapter.js +56 -3
  43. package/dist/adapters/postgresql/PostgresAdapter.js.map +1 -1
  44. package/dist/adapters/postgresql/prompts/ltree.js +2 -2
  45. package/dist/adapters/postgresql/prompts/ltree.js.map +1 -1
  46. package/dist/adapters/postgresql/schemas/admin.d.ts +10 -5
  47. package/dist/adapters/postgresql/schemas/admin.d.ts.map +1 -1
  48. package/dist/adapters/postgresql/schemas/admin.js +10 -5
  49. package/dist/adapters/postgresql/schemas/admin.js.map +1 -1
  50. package/dist/adapters/postgresql/schemas/backup.d.ts +45 -27
  51. package/dist/adapters/postgresql/schemas/backup.d.ts.map +1 -1
  52. package/dist/adapters/postgresql/schemas/backup.js +64 -26
  53. package/dist/adapters/postgresql/schemas/backup.js.map +1 -1
  54. package/dist/adapters/postgresql/schemas/core.d.ts +53 -19
  55. package/dist/adapters/postgresql/schemas/core.d.ts.map +1 -1
  56. package/dist/adapters/postgresql/schemas/core.js +61 -17
  57. package/dist/adapters/postgresql/schemas/core.js.map +1 -1
  58. package/dist/adapters/postgresql/schemas/cron.d.ts +51 -32
  59. package/dist/adapters/postgresql/schemas/cron.d.ts.map +1 -1
  60. package/dist/adapters/postgresql/schemas/cron.js +64 -44
  61. package/dist/adapters/postgresql/schemas/cron.js.map +1 -1
  62. package/dist/adapters/postgresql/schemas/extensions.d.ts +224 -110
  63. package/dist/adapters/postgresql/schemas/extensions.d.ts.map +1 -1
  64. package/dist/adapters/postgresql/schemas/extensions.js +245 -96
  65. package/dist/adapters/postgresql/schemas/extensions.js.map +1 -1
  66. package/dist/adapters/postgresql/schemas/index.d.ts +7 -6
  67. package/dist/adapters/postgresql/schemas/index.d.ts.map +1 -1
  68. package/dist/adapters/postgresql/schemas/index.js +16 -8
  69. package/dist/adapters/postgresql/schemas/index.js.map +1 -1
  70. package/dist/adapters/postgresql/schemas/introspection.d.ts +445 -0
  71. package/dist/adapters/postgresql/schemas/introspection.d.ts.map +1 -0
  72. package/dist/adapters/postgresql/schemas/introspection.js +478 -0
  73. package/dist/adapters/postgresql/schemas/introspection.js.map +1 -0
  74. package/dist/adapters/postgresql/schemas/jsonb.d.ts +102 -42
  75. package/dist/adapters/postgresql/schemas/jsonb.d.ts.map +1 -1
  76. package/dist/adapters/postgresql/schemas/jsonb.js +125 -30
  77. package/dist/adapters/postgresql/schemas/jsonb.js.map +1 -1
  78. package/dist/adapters/postgresql/schemas/monitoring.d.ts +69 -36
  79. package/dist/adapters/postgresql/schemas/monitoring.d.ts.map +1 -1
  80. package/dist/adapters/postgresql/schemas/monitoring.js +98 -40
  81. package/dist/adapters/postgresql/schemas/monitoring.js.map +1 -1
  82. package/dist/adapters/postgresql/schemas/partitioning.d.ts +21 -24
  83. package/dist/adapters/postgresql/schemas/partitioning.d.ts.map +1 -1
  84. package/dist/adapters/postgresql/schemas/partitioning.js +26 -14
  85. package/dist/adapters/postgresql/schemas/partitioning.js.map +1 -1
  86. package/dist/adapters/postgresql/schemas/partman.d.ts +69 -0
  87. package/dist/adapters/postgresql/schemas/partman.d.ts.map +1 -1
  88. package/dist/adapters/postgresql/schemas/partman.js +46 -33
  89. package/dist/adapters/postgresql/schemas/partman.js.map +1 -1
  90. package/dist/adapters/postgresql/schemas/performance.d.ts +97 -49
  91. package/dist/adapters/postgresql/schemas/performance.d.ts.map +1 -1
  92. package/dist/adapters/postgresql/schemas/performance.js +139 -34
  93. package/dist/adapters/postgresql/schemas/performance.js.map +1 -1
  94. package/dist/adapters/postgresql/schemas/postgis.d.ts +20 -0
  95. package/dist/adapters/postgresql/schemas/postgis.d.ts.map +1 -1
  96. package/dist/adapters/postgresql/schemas/postgis.js +40 -0
  97. package/dist/adapters/postgresql/schemas/postgis.js.map +1 -1
  98. package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts +50 -30
  99. package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts.map +1 -1
  100. package/dist/adapters/postgresql/schemas/schema-mgmt.js +105 -33
  101. package/dist/adapters/postgresql/schemas/schema-mgmt.js.map +1 -1
  102. package/dist/adapters/postgresql/schemas/stats.d.ts +33 -20
  103. package/dist/adapters/postgresql/schemas/stats.d.ts.map +1 -1
  104. package/dist/adapters/postgresql/schemas/stats.js +36 -20
  105. package/dist/adapters/postgresql/schemas/stats.js.map +1 -1
  106. package/dist/adapters/postgresql/schemas/text-search.d.ts +34 -19
  107. package/dist/adapters/postgresql/schemas/text-search.d.ts.map +1 -1
  108. package/dist/adapters/postgresql/schemas/text-search.js +52 -13
  109. package/dist/adapters/postgresql/schemas/text-search.js.map +1 -1
  110. package/dist/adapters/postgresql/tools/admin.d.ts.map +1 -1
  111. package/dist/adapters/postgresql/tools/admin.js +272 -186
  112. package/dist/adapters/postgresql/tools/admin.js.map +1 -1
  113. package/dist/adapters/postgresql/tools/backup/dump.d.ts.map +1 -1
  114. package/dist/adapters/postgresql/tools/backup/dump.js +376 -350
  115. package/dist/adapters/postgresql/tools/backup/dump.js.map +1 -1
  116. package/dist/adapters/postgresql/tools/citext.d.ts.map +1 -1
  117. package/dist/adapters/postgresql/tools/citext.js +333 -243
  118. package/dist/adapters/postgresql/tools/citext.js.map +1 -1
  119. package/dist/adapters/postgresql/tools/codemode/index.d.ts.map +1 -1
  120. package/dist/adapters/postgresql/tools/codemode/index.js +2 -11
  121. package/dist/adapters/postgresql/tools/codemode/index.js.map +1 -1
  122. package/dist/adapters/postgresql/tools/core/convenience.d.ts +9 -1
  123. package/dist/adapters/postgresql/tools/core/convenience.d.ts.map +1 -1
  124. package/dist/adapters/postgresql/tools/core/convenience.js +101 -19
  125. package/dist/adapters/postgresql/tools/core/convenience.js.map +1 -1
  126. package/dist/adapters/postgresql/tools/core/error-helpers.d.ts +48 -0
  127. package/dist/adapters/postgresql/tools/core/error-helpers.d.ts.map +1 -0
  128. package/dist/adapters/postgresql/tools/core/error-helpers.js +256 -0
  129. package/dist/adapters/postgresql/tools/core/error-helpers.js.map +1 -0
  130. package/dist/adapters/postgresql/tools/core/health.d.ts.map +1 -1
  131. package/dist/adapters/postgresql/tools/core/health.js +18 -4
  132. package/dist/adapters/postgresql/tools/core/health.js.map +1 -1
  133. package/dist/adapters/postgresql/tools/core/indexes.d.ts.map +1 -1
  134. package/dist/adapters/postgresql/tools/core/indexes.js +48 -6
  135. package/dist/adapters/postgresql/tools/core/indexes.js.map +1 -1
  136. package/dist/adapters/postgresql/tools/core/objects.d.ts.map +1 -1
  137. package/dist/adapters/postgresql/tools/core/objects.js +104 -85
  138. package/dist/adapters/postgresql/tools/core/objects.js.map +1 -1
  139. package/dist/adapters/postgresql/tools/core/query.d.ts.map +1 -1
  140. package/dist/adapters/postgresql/tools/core/query.js +100 -42
  141. package/dist/adapters/postgresql/tools/core/query.js.map +1 -1
  142. package/dist/adapters/postgresql/tools/core/schemas.d.ts +51 -25
  143. package/dist/adapters/postgresql/tools/core/schemas.d.ts.map +1 -1
  144. package/dist/adapters/postgresql/tools/core/schemas.js +51 -25
  145. package/dist/adapters/postgresql/tools/core/schemas.js.map +1 -1
  146. package/dist/adapters/postgresql/tools/core/tables.d.ts.map +1 -1
  147. package/dist/adapters/postgresql/tools/core/tables.js +72 -32
  148. package/dist/adapters/postgresql/tools/core/tables.js.map +1 -1
  149. package/dist/adapters/postgresql/tools/cron.d.ts.map +1 -1
  150. package/dist/adapters/postgresql/tools/cron.js +333 -206
  151. package/dist/adapters/postgresql/tools/cron.js.map +1 -1
  152. package/dist/adapters/postgresql/tools/introspection.d.ts +15 -0
  153. package/dist/adapters/postgresql/tools/introspection.d.ts.map +1 -0
  154. package/dist/adapters/postgresql/tools/introspection.js +1682 -0
  155. package/dist/adapters/postgresql/tools/introspection.js.map +1 -0
  156. package/dist/adapters/postgresql/tools/jsonb/advanced.d.ts.map +1 -1
  157. package/dist/adapters/postgresql/tools/jsonb/advanced.js +394 -297
  158. package/dist/adapters/postgresql/tools/jsonb/advanced.js.map +1 -1
  159. package/dist/adapters/postgresql/tools/jsonb/basic.d.ts.map +1 -1
  160. package/dist/adapters/postgresql/tools/jsonb/basic.js +686 -398
  161. package/dist/adapters/postgresql/tools/jsonb/basic.js.map +1 -1
  162. package/dist/adapters/postgresql/tools/kcache.d.ts.map +1 -1
  163. package/dist/adapters/postgresql/tools/kcache.js +278 -246
  164. package/dist/adapters/postgresql/tools/kcache.js.map +1 -1
  165. package/dist/adapters/postgresql/tools/ltree.d.ts.map +1 -1
  166. package/dist/adapters/postgresql/tools/ltree.js +137 -38
  167. package/dist/adapters/postgresql/tools/ltree.js.map +1 -1
  168. package/dist/adapters/postgresql/tools/monitoring.d.ts.map +1 -1
  169. package/dist/adapters/postgresql/tools/monitoring.js +86 -55
  170. package/dist/adapters/postgresql/tools/monitoring.js.map +1 -1
  171. package/dist/adapters/postgresql/tools/partitioning.d.ts.map +1 -1
  172. package/dist/adapters/postgresql/tools/partitioning.js +79 -15
  173. package/dist/adapters/postgresql/tools/partitioning.js.map +1 -1
  174. package/dist/adapters/postgresql/tools/partman/management.d.ts.map +1 -1
  175. package/dist/adapters/postgresql/tools/partman/management.js +43 -56
  176. package/dist/adapters/postgresql/tools/partman/management.js.map +1 -1
  177. package/dist/adapters/postgresql/tools/partman/operations.d.ts.map +1 -1
  178. package/dist/adapters/postgresql/tools/partman/operations.js +137 -24
  179. package/dist/adapters/postgresql/tools/partman/operations.js.map +1 -1
  180. package/dist/adapters/postgresql/tools/performance/analysis.d.ts.map +1 -1
  181. package/dist/adapters/postgresql/tools/performance/analysis.js +276 -165
  182. package/dist/adapters/postgresql/tools/performance/analysis.js.map +1 -1
  183. package/dist/adapters/postgresql/tools/performance/explain.d.ts.map +1 -1
  184. package/dist/adapters/postgresql/tools/performance/explain.js +61 -21
  185. package/dist/adapters/postgresql/tools/performance/explain.js.map +1 -1
  186. package/dist/adapters/postgresql/tools/performance/monitoring.d.ts.map +1 -1
  187. package/dist/adapters/postgresql/tools/performance/monitoring.js +52 -12
  188. package/dist/adapters/postgresql/tools/performance/monitoring.js.map +1 -1
  189. package/dist/adapters/postgresql/tools/performance/optimization.d.ts.map +1 -1
  190. package/dist/adapters/postgresql/tools/performance/optimization.js +92 -81
  191. package/dist/adapters/postgresql/tools/performance/optimization.js.map +1 -1
  192. package/dist/adapters/postgresql/tools/performance/stats.d.ts.map +1 -1
  193. package/dist/adapters/postgresql/tools/performance/stats.js +182 -60
  194. package/dist/adapters/postgresql/tools/performance/stats.js.map +1 -1
  195. package/dist/adapters/postgresql/tools/pgcrypto.d.ts.map +1 -1
  196. package/dist/adapters/postgresql/tools/pgcrypto.js +277 -102
  197. package/dist/adapters/postgresql/tools/pgcrypto.js.map +1 -1
  198. package/dist/adapters/postgresql/tools/postgis/advanced.d.ts.map +1 -1
  199. package/dist/adapters/postgresql/tools/postgis/advanced.js +298 -230
  200. package/dist/adapters/postgresql/tools/postgis/advanced.js.map +1 -1
  201. package/dist/adapters/postgresql/tools/postgis/basic.d.ts.map +1 -1
  202. package/dist/adapters/postgresql/tools/postgis/basic.js +370 -251
  203. package/dist/adapters/postgresql/tools/postgis/basic.js.map +1 -1
  204. package/dist/adapters/postgresql/tools/postgis/standalone.d.ts.map +1 -1
  205. package/dist/adapters/postgresql/tools/postgis/standalone.js +135 -51
  206. package/dist/adapters/postgresql/tools/postgis/standalone.js.map +1 -1
  207. package/dist/adapters/postgresql/tools/schema.d.ts.map +1 -1
  208. package/dist/adapters/postgresql/tools/schema.js +580 -233
  209. package/dist/adapters/postgresql/tools/schema.js.map +1 -1
  210. package/dist/adapters/postgresql/tools/stats/advanced.d.ts.map +1 -1
  211. package/dist/adapters/postgresql/tools/stats/advanced.js +567 -506
  212. package/dist/adapters/postgresql/tools/stats/advanced.js.map +1 -1
  213. package/dist/adapters/postgresql/tools/stats/basic.d.ts.map +1 -1
  214. package/dist/adapters/postgresql/tools/stats/basic.js +340 -316
  215. package/dist/adapters/postgresql/tools/stats/basic.js.map +1 -1
  216. package/dist/adapters/postgresql/tools/text.d.ts.map +1 -1
  217. package/dist/adapters/postgresql/tools/text.js +690 -337
  218. package/dist/adapters/postgresql/tools/text.js.map +1 -1
  219. package/dist/adapters/postgresql/tools/transactions.d.ts.map +1 -1
  220. package/dist/adapters/postgresql/tools/transactions.js +157 -50
  221. package/dist/adapters/postgresql/tools/transactions.js.map +1 -1
  222. package/dist/adapters/postgresql/tools/vector/advanced.d.ts.map +1 -1
  223. package/dist/adapters/postgresql/tools/vector/advanced.js +18 -0
  224. package/dist/adapters/postgresql/tools/vector/advanced.js.map +1 -1
  225. package/dist/adapters/postgresql/tools/vector/basic.d.ts.map +1 -1
  226. package/dist/adapters/postgresql/tools/vector/basic.js +100 -53
  227. package/dist/adapters/postgresql/tools/vector/basic.js.map +1 -1
  228. package/dist/auth/auth-context.d.ts +28 -0
  229. package/dist/auth/auth-context.d.ts.map +1 -0
  230. package/dist/auth/auth-context.js +37 -0
  231. package/dist/auth/auth-context.js.map +1 -0
  232. package/dist/auth/scope-map.d.ts +20 -0
  233. package/dist/auth/scope-map.d.ts.map +1 -0
  234. package/dist/auth/scope-map.js +40 -0
  235. package/dist/auth/scope-map.js.map +1 -0
  236. package/dist/auth/scopes.d.ts.map +1 -1
  237. package/dist/auth/scopes.js +2 -0
  238. package/dist/auth/scopes.js.map +1 -1
  239. package/dist/cli.js +1 -1
  240. package/dist/cli.js.map +1 -1
  241. package/dist/codemode/api.d.ts +1 -0
  242. package/dist/codemode/api.d.ts.map +1 -1
  243. package/dist/codemode/api.js +35 -1
  244. package/dist/codemode/api.js.map +1 -1
  245. package/dist/codemode/index.d.ts +0 -2
  246. package/dist/codemode/index.d.ts.map +1 -1
  247. package/dist/codemode/index.js +0 -4
  248. package/dist/codemode/index.js.map +1 -1
  249. package/dist/codemode/sandbox.d.ts +14 -1
  250. package/dist/codemode/sandbox.d.ts.map +1 -1
  251. package/dist/codemode/sandbox.js +58 -19
  252. package/dist/codemode/sandbox.js.map +1 -1
  253. package/dist/codemode/types.d.ts.map +1 -1
  254. package/dist/codemode/types.js +3 -0
  255. package/dist/codemode/types.js.map +1 -1
  256. package/dist/constants/ServerInstructions.d.ts +5 -1
  257. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  258. package/dist/constants/ServerInstructions.js +117 -31
  259. package/dist/constants/ServerInstructions.js.map +1 -1
  260. package/dist/filtering/ToolConstants.d.ts +22 -19
  261. package/dist/filtering/ToolConstants.d.ts.map +1 -1
  262. package/dist/filtering/ToolConstants.js +48 -37
  263. package/dist/filtering/ToolConstants.js.map +1 -1
  264. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  265. package/dist/filtering/ToolFilter.js +10 -13
  266. package/dist/filtering/ToolFilter.js.map +1 -1
  267. package/dist/pool/ConnectionPool.js +1 -1
  268. package/dist/pool/ConnectionPool.js.map +1 -1
  269. package/dist/transports/http.d.ts +1 -0
  270. package/dist/transports/http.d.ts.map +1 -1
  271. package/dist/transports/http.js +75 -21
  272. package/dist/transports/http.js.map +1 -1
  273. package/dist/types/filtering.d.ts +2 -2
  274. package/dist/types/filtering.d.ts.map +1 -1
  275. package/dist/utils/icons.d.ts.map +1 -1
  276. package/dist/utils/icons.js +5 -0
  277. package/dist/utils/icons.js.map +1 -1
  278. package/dist/utils/where-clause.d.ts.map +1 -1
  279. package/dist/utils/where-clause.js +24 -0
  280. package/dist/utils/where-clause.js.map +1 -1
  281. package/package.json +20 -13
  282. package/dist/codemode/sandbox-factory.d.ts +0 -72
  283. package/dist/codemode/sandbox-factory.d.ts.map +0 -1
  284. package/dist/codemode/sandbox-factory.js +0 -88
  285. package/dist/codemode/sandbox-factory.js.map +0 -1
  286. package/dist/codemode/worker-sandbox.d.ts +0 -82
  287. package/dist/codemode/worker-sandbox.d.ts.map +0 -1
  288. package/dist/codemode/worker-sandbox.js +0 -244
  289. package/dist/codemode/worker-sandbox.js.map +0 -1
  290. package/dist/codemode/worker-script.d.ts +0 -8
  291. package/dist/codemode/worker-script.d.ts.map +0 -1
  292. package/dist/codemode/worker-script.js +0 -113
  293. 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
- const { table, column, schema, where, params: queryParams, groupBy, } = StatsDescriptiveSchema.parse(params);
32
- const schemaPrefix = schema ? `"${schema}".` : "";
33
- const whereClause = where ? `WHERE ${where}` : "";
34
- // Validate column is numeric type
35
- const typeCheckQuery = `
36
- SELECT data_type
37
- FROM information_schema.columns
38
- WHERE table_schema = '${schema ?? "public"}'
39
- AND table_name = '${table}'
40
- AND column_name = '${column}'
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
- const typeResult = await adapter.executeQuery(typeCheckQuery);
43
- const typeRow = typeResult.rows?.[0];
44
- if (!typeRow) {
45
- // Check if table exists
46
- const tableCheckQuery = `
47
- SELECT 1 FROM information_schema.tables
48
- WHERE table_schema = '${schema ?? "public"}' AND table_name = '${table}'
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
- const tableResult = await adapter.executeQuery(tableCheckQuery);
51
- if (tableResult.rows?.length === 0) {
52
- throw new Error(`Table "${schema ?? "public"}.${table}" not found`);
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
- throw new Error(`Column "${column}" not found in table "${schema ?? "public"}.${table}"`);
55
- }
56
- const numericTypes = [
57
- "integer",
58
- "bigint",
59
- "smallint",
60
- "numeric",
61
- "decimal",
62
- "real",
63
- "double precision",
64
- "money",
65
- ];
66
- if (!numericTypes.includes(typeRow.data_type)) {
67
- throw new Error(`Column "${column}" is type "${typeRow.data_type}" but must be a numeric type for statistical analysis`);
68
- }
69
- // Helper to map stats row to numeric object
70
- const mapStats = (row) => ({
71
- count: Number(row["count"]),
72
- min: row["min"] !== null ? Number(row["min"]) : null,
73
- max: row["max"] !== null ? Number(row["max"]) : null,
74
- avg: row["avg"] !== null ? Number(row["avg"]) : null,
75
- stddev: row["stddev"] !== null ? Number(row["stddev"]) : null,
76
- variance: row["variance"] !== null ? Number(row["variance"]) : null,
77
- sum: row["sum"] !== null ? Number(row["sum"]) : null,
78
- mode: row["mode"] !== null && row["mode"] !== undefined
79
- ? Number(row["mode"])
80
- : null,
81
- });
82
- if (groupBy !== undefined) {
83
- // Grouped statistics
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
- const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
101
- ? [queryParams]
102
- : []));
103
- const rows = result.rows ?? [];
104
- const groups = rows.map((row) => ({
105
- groupKey: row["group_key"],
106
- statistics: mapStats(row),
107
- }));
108
- return {
109
- table: `${schema ?? "public"}.${table}`,
110
- column,
111
- groupBy,
112
- groups,
113
- count: groups.length,
114
- };
115
- }
116
- // Ungrouped statistics (original behavior)
117
- const sql = `
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
- const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
131
- ? [queryParams]
132
- : []));
133
- const stats = result.rows?.[0];
134
- if (!stats)
135
- throw new Error("No stats found");
136
- return {
137
- table: `${schema ?? "public"}.${table}`,
138
- column,
139
- statistics: mapStats(stats),
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 = '${schema}'
163
- AND table_name = '${table}'
164
- AND column_name = '${column}'
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 = '${schema}' AND table_name = '${table}'
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
- const parsed = StatsPercentilesSchema.parse(params);
198
- const { table, column, percentiles, schema, where, params: queryParams, groupBy, _percentileScaleWarning, } = parsed;
199
- const schemaName = schema ?? "public";
200
- // Validate column exists and is numeric
201
- await validateNumericColumn(adapter, table, column, schemaName);
202
- const pctiles = percentiles ?? [0.25, 0.5, 0.75];
203
- const schemaPrefix = schema ? `"${schema}".` : "";
204
- const whereClause = where ? `WHERE ${where}` : "";
205
- const percentileSelects = pctiles
206
- .map((p) => `PERCENTILE_CONT(${String(p)}) WITHIN GROUP (ORDER BY "${column}") as p${String(Math.round(p * 100))}`)
207
- .join(",\n ");
208
- // Helper to map row to percentile results (round to 6 decimal places to avoid floating-point artifacts)
209
- const mapPercentiles = (row) => {
210
- const result = {};
211
- for (const p of pctiles) {
212
- const key = `p${String(Math.round(p * 100))}`;
213
- const val = row[key] !== null && row[key] !== undefined
214
- ? Number(row[key])
215
- : null;
216
- result[key] = val !== null ? Math.round(val * 1e6) / 1e6 : null;
217
- }
218
- return result;
219
- };
220
- if (groupBy !== undefined) {
221
- // Grouped percentiles
222
- const sql = `
223
- SELECT
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 rows = result.rows ?? [];
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
- groupBy,
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
- // Ungrouped percentiles
253
- const sql = `
254
- SELECT
255
- ${percentileSelects}
256
- FROM ${schemaPrefix}"${table}"
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
- const parsed = StatsCorrelationSchema.parse(params);
290
- const { table, column1, column2, schema, where, params: queryParams, groupBy, } = parsed;
291
- const schemaPrefix = schema ? `"${schema}".` : "";
292
- const whereClause = where ? `WHERE ${where}` : "";
293
- // Validate both columns are numeric types
294
- const numericTypes = [
295
- "integer",
296
- "bigint",
297
- "smallint",
298
- "numeric",
299
- "decimal",
300
- "real",
301
- "double precision",
302
- "money",
303
- ];
304
- for (const col of [column1, column2]) {
305
- const typeCheckQuery = `
306
- SELECT data_type
307
- FROM information_schema.columns
308
- WHERE table_schema = '${schema ?? "public"}'
309
- AND table_name = '${table}'
310
- AND column_name = '${col}'
311
- `;
312
- const typeResult = await adapter.executeQuery(typeCheckQuery);
313
- const typeRow = typeResult.rows?.[0];
314
- if (!typeRow) {
315
- throw new Error(`Column "${col}" not found in table "${schema ?? "public"}.${table}"`);
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
- if (groupBy !== undefined) {
356
- // Grouped correlation
357
- const sql = `
358
- SELECT
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
- const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
370
- ? [queryParams]
371
- : []));
372
- const rows = result.rows ?? [];
373
- const groups = rows.map((row) => ({
374
- groupKey: row["group_key"],
375
- ...mapCorrelation(row),
376
- }));
377
- return {
378
- table: `${schema ?? "public"}.${table}`,
379
- columns: [column1, column2],
380
- groupBy,
381
- groups,
382
- count: groups.length,
383
- };
384
- }
385
- // Ungrouped correlation
386
- const sql = `
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
- const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
396
- ? [queryParams]
397
- : []));
398
- const row = result.rows?.[0];
399
- if (!row)
400
- throw new Error("No correlation data found");
401
- const response = {
402
- table: `${schema ?? "public"}.${table}`,
403
- columns: [column1, column2],
404
- ...mapCorrelation(row),
405
- };
406
- // Add note for self-correlation
407
- if (column1 === column2) {
408
- response["note"] = "Self-correlation always equals 1.0";
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
- const parsed = StatsRegressionSchema.parse(params);
428
- const { table, xColumn, yColumn, schema, where, params: queryParams, groupBy, } = parsed;
429
- const schemaName = schema ?? "public";
430
- const schemaPrefix = schema ? `"${schema}".` : "";
431
- const whereClause = where ? `WHERE ${where}` : "";
432
- // Validate both columns exist and are numeric
433
- await validateNumericColumn(adapter, table, xColumn, schemaName);
434
- await validateNumericColumn(adapter, table, yColumn, schemaName);
435
- // Helper to map row to regression result
436
- const mapRegression = (row) => {
437
- const slope = row["slope"] !== null ? Number(row["slope"]) : null;
438
- const intercept = row["intercept"] !== null ? Number(row["intercept"]) : null;
439
- const rSquared = row["r_squared"] !== null ? Number(row["r_squared"]) : null;
440
- let equation = "N/A";
441
- if (slope !== null && intercept !== null) {
442
- const sign = intercept >= 0 ? "+" : "-";
443
- equation = `y = ${slope.toFixed(4)}x ${sign} ${Math.abs(intercept).toFixed(4)}`;
444
- }
445
- return {
446
- slope,
447
- intercept,
448
- rSquared,
449
- equation,
450
- avgX: row["avg_x"] !== null ? Number(row["avg_x"]) : null,
451
- avgY: row["avg_y"] !== null ? Number(row["avg_y"]) : null,
452
- sampleSize: Number(row["sample_size"]),
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
- if (groupBy !== undefined) {
456
- // Grouped regression
457
- const sql = `
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
- const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
472
- ? [queryParams]
473
- : []));
474
- const rows = result.rows ?? [];
475
- const groups = rows.map((row) => ({
476
- groupKey: row["group_key"],
477
- regression: mapRegression(row),
478
- }));
479
- return {
480
- table: `${schema ?? "public"}.${table}`,
481
- xColumn,
482
- yColumn,
483
- groupBy,
484
- groups,
485
- count: groups.length,
486
- };
487
- }
488
- // Ungrouped regression
489
- const sql = `
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
- const result = await adapter.executeQuery(sql, ...(queryParams !== undefined && queryParams.length > 0
504
- ? [queryParams]
505
- : []));
506
- const row = result.rows?.[0];
507
- if (!row)
508
- return { error: "No regression data found" };
509
- const response = {
510
- table: `${schema ?? "public"}.${table}`,
511
- xColumn,
512
- yColumn,
513
- regression: mapRegression(row),
514
- };
515
- // Add note for self-regression
516
- if (xColumn === yColumn) {
517
- response["note"] = "Self-regression always returns slope=1, r²=1";
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
  }