@neverinfamous/postgres-mcp 1.1.0 → 1.3.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 +95 -81
- package/dist/__tests__/mocks/adapter.d.ts.map +1 -1
- package/dist/__tests__/mocks/adapter.js +0 -1
- package/dist/__tests__/mocks/adapter.js.map +1 -1
- package/dist/__tests__/mocks/pool.d.ts.map +1 -1
- package/dist/__tests__/mocks/pool.js +0 -1
- package/dist/__tests__/mocks/pool.js.map +1 -1
- package/dist/adapters/DatabaseAdapter.js +1 -1
- package/dist/adapters/DatabaseAdapter.js.map +1 -1
- package/dist/adapters/postgresql/PostgresAdapter.d.ts.map +1 -1
- package/dist/adapters/postgresql/PostgresAdapter.js +78 -8
- package/dist/adapters/postgresql/PostgresAdapter.js.map +1 -1
- package/dist/adapters/postgresql/prompts/backup.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/backup.js +2 -3
- package/dist/adapters/postgresql/prompts/backup.js.map +1 -1
- package/dist/adapters/postgresql/prompts/citext.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/citext.js +3 -4
- package/dist/adapters/postgresql/prompts/citext.js.map +1 -1
- package/dist/adapters/postgresql/prompts/extensionSetup.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/extensionSetup.js +2 -3
- package/dist/adapters/postgresql/prompts/extensionSetup.js.map +1 -1
- package/dist/adapters/postgresql/prompts/health.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/health.js +2 -3
- package/dist/adapters/postgresql/prompts/health.js.map +1 -1
- package/dist/adapters/postgresql/prompts/index.js +20 -27
- package/dist/adapters/postgresql/prompts/index.js.map +1 -1
- package/dist/adapters/postgresql/prompts/indexTuning.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/indexTuning.js +2 -3
- package/dist/adapters/postgresql/prompts/indexTuning.js.map +1 -1
- package/dist/adapters/postgresql/prompts/kcache.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/kcache.js +3 -4
- package/dist/adapters/postgresql/prompts/kcache.js.map +1 -1
- package/dist/adapters/postgresql/prompts/ltree.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/ltree.js +5 -6
- package/dist/adapters/postgresql/prompts/ltree.js.map +1 -1
- package/dist/adapters/postgresql/prompts/partman.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/partman.js +2 -3
- package/dist/adapters/postgresql/prompts/partman.js.map +1 -1
- package/dist/adapters/postgresql/prompts/pgcron.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/pgcron.js +2 -3
- package/dist/adapters/postgresql/prompts/pgcron.js.map +1 -1
- package/dist/adapters/postgresql/prompts/pgcrypto.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/pgcrypto.js +3 -4
- package/dist/adapters/postgresql/prompts/pgcrypto.js.map +1 -1
- package/dist/adapters/postgresql/prompts/pgvector.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/pgvector.js +3 -4
- package/dist/adapters/postgresql/prompts/pgvector.js.map +1 -1
- package/dist/adapters/postgresql/prompts/postgis.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/postgis.js +2 -3
- package/dist/adapters/postgresql/prompts/postgis.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 +8 -4
- package/dist/adapters/postgresql/schemas/backup.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/backup.js +11 -4
- package/dist/adapters/postgresql/schemas/backup.js.map +1 -1
- package/dist/adapters/postgresql/schemas/core.d.ts +54 -19
- package/dist/adapters/postgresql/schemas/core.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/core.js +65 -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 +168 -73
- package/dist/adapters/postgresql/schemas/extensions.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/extensions.js +179 -62
- package/dist/adapters/postgresql/schemas/extensions.js.map +1 -1
- package/dist/adapters/postgresql/schemas/index.d.ts +5 -5
- package/dist/adapters/postgresql/schemas/index.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/index.js +9 -7
- package/dist/adapters/postgresql/schemas/index.js.map +1 -1
- package/dist/adapters/postgresql/schemas/jsonb.d.ts +94 -42
- package/dist/adapters/postgresql/schemas/jsonb.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/jsonb.js +101 -30
- package/dist/adapters/postgresql/schemas/jsonb.js.map +1 -1
- package/dist/adapters/postgresql/schemas/monitoring.d.ts +28 -11
- package/dist/adapters/postgresql/schemas/monitoring.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/monitoring.js +49 -24
- package/dist/adapters/postgresql/schemas/monitoring.js.map +1 -1
- package/dist/adapters/postgresql/schemas/partitioning.d.ts +15 -11
- package/dist/adapters/postgresql/schemas/partitioning.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/partitioning.js +17 -13
- package/dist/adapters/postgresql/schemas/partitioning.js.map +1 -1
- package/dist/adapters/postgresql/schemas/performance.d.ts +62 -31
- package/dist/adapters/postgresql/schemas/performance.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/performance.js +86 -24
- 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 +20 -3
- package/dist/adapters/postgresql/schemas/postgis.js.map +1 -1
- package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts +35 -23
- package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/schema-mgmt.js +69 -26
- 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 +8 -5
- package/dist/adapters/postgresql/schemas/text-search.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/text-search.js +15 -5
- 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 +211 -140
- 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 +410 -387
- package/dist/adapters/postgresql/tools/backup/dump.js.map +1 -1
- package/dist/adapters/postgresql/tools/backup/planning.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/backup/planning.js +175 -172
- package/dist/adapters/postgresql/tools/backup/planning.js.map +1 -1
- package/dist/adapters/postgresql/tools/citext.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/citext.js +221 -163
- package/dist/adapters/postgresql/tools/citext.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 +96 -9
- 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 +23 -6
- 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 +45 -4
- 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 +52 -25
- package/dist/adapters/postgresql/tools/core/schemas.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/schemas.js +55 -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 +74 -30
- 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 +274 -179
- package/dist/adapters/postgresql/tools/cron.js.map +1 -1
- package/dist/adapters/postgresql/tools/jsonb/advanced.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/jsonb/advanced.js +372 -284
- 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 +617 -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 +282 -220
- 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 +126 -35
- 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 +59 -40
- 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 +150 -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 +12 -5
- 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 +135 -22
- 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 +264 -160
- 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 +44 -7
- 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 +128 -37
- 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 +242 -87
- 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 +293 -201
- 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 +359 -249
- 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 +515 -226
- 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 +515 -476
- 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 +302 -293
- 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 +398 -220
- 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 +70 -38
- package/dist/adapters/postgresql/tools/vector/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/vector/basic.d.ts +8 -0
- package/dist/adapters/postgresql/tools/vector/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/vector/basic.js +194 -82
- package/dist/adapters/postgresql/tools/vector/basic.js.map +1 -1
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +15 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli.js +7 -6
- package/dist/cli.js.map +1 -1
- package/dist/codemode/api.d.ts.map +1 -1
- package/dist/codemode/api.js +4 -3
- package/dist/codemode/api.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +76 -34
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/filtering/ToolConstants.d.ts +29 -13
- package/dist/filtering/ToolConstants.d.ts.map +1 -1
- package/dist/filtering/ToolConstants.js +44 -27
- package/dist/filtering/ToolConstants.js.map +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/progress-utils.js +1 -1
- package/dist/utils/progress-utils.js.map +1 -1
- package/package.json +13 -9
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Text processing, FTS, trigrams, and fuzzy matching.
|
|
5
5
|
* 14 tools total.
|
|
6
6
|
*/
|
|
7
|
-
import { z } from "zod";
|
|
7
|
+
import { z, ZodError } from "zod";
|
|
8
8
|
import { readOnly, write } from "../../../utils/annotations.js";
|
|
9
9
|
import { getToolIcons } from "../../../utils/icons.js";
|
|
10
|
+
import { formatPostgresError } from "./core/error-helpers.js";
|
|
10
11
|
import { sanitizeIdentifier, sanitizeIdentifiers, sanitizeTableName, } from "../../../utils/identifiers.js";
|
|
11
12
|
import { sanitizeFtsConfig } from "../../../utils/fts-config.js";
|
|
12
13
|
import { sanitizeWhereClause } from "../../../utils/where-clause.js";
|
|
@@ -43,43 +44,65 @@ function createTextSearchTool(adapter) {
|
|
|
43
44
|
annotations: readOnly("Full-Text Search"),
|
|
44
45
|
icons: getToolIcons("text", readOnly("Full-Text Search")),
|
|
45
46
|
handler: async (params, _context) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
47
|
+
try {
|
|
48
|
+
const parsed = TextSearchSchema.parse(params);
|
|
49
|
+
const cfg = sanitizeFtsConfig(parsed.config ?? "english");
|
|
50
|
+
// Handle both column (string) and columns (array) parameters
|
|
51
|
+
// The preprocessor converts column → columns, but we handle both for safety
|
|
52
|
+
let cols;
|
|
53
|
+
if (parsed.columns !== undefined && parsed.columns.length > 0) {
|
|
54
|
+
cols = parsed.columns;
|
|
55
|
+
}
|
|
56
|
+
else if (parsed.column !== undefined) {
|
|
57
|
+
cols = [parsed.column];
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: "Either 'columns' (array) or 'column' (string) is required",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Build qualified table name with schema support
|
|
66
|
+
// The preprocessor guarantees table is set (converts tableName → table)
|
|
67
|
+
const resolvedTable = parsed.table ?? parsed.tableName;
|
|
68
|
+
if (!resolvedTable) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: "Either 'table' or 'tableName' is required",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
75
|
+
const sanitizedCols = sanitizeIdentifiers(cols);
|
|
76
|
+
const selectCols = parsed.select !== undefined && parsed.select.length > 0
|
|
77
|
+
? sanitizeIdentifiers(parsed.select).join(", ")
|
|
78
|
+
: "*";
|
|
79
|
+
const tsvector = sanitizedCols
|
|
80
|
+
.map((c) => `coalesce(${c}, '')`)
|
|
81
|
+
.join(" || ' ' || ");
|
|
82
|
+
const limitClause = parsed.limit !== undefined && parsed.limit > 0
|
|
83
|
+
? ` LIMIT ${String(parsed.limit)}`
|
|
84
|
+
: "";
|
|
85
|
+
const sql = `SELECT ${selectCols}, ts_rank_cd(to_tsvector('${cfg}', ${tsvector}), plainto_tsquery('${cfg}', $1)) as rank
|
|
78
86
|
FROM ${tableName}
|
|
79
87
|
WHERE to_tsvector('${cfg}', ${tsvector}) @@ plainto_tsquery('${cfg}', $1)
|
|
80
88
|
ORDER BY rank DESC${limitClause}`;
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
const result = await adapter.executeQuery(sql, [parsed.query]);
|
|
90
|
+
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error instanceof ZodError) {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: `pg_text_search validation error: ${error.issues.map((e) => e.message).join(", ")}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: formatPostgresError(error, {
|
|
102
|
+
tool: "pg_text_search",
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
83
106
|
},
|
|
84
107
|
};
|
|
85
108
|
}
|
|
@@ -115,42 +138,64 @@ function createTextRankTool(adapter) {
|
|
|
115
138
|
annotations: readOnly("Text Rank"),
|
|
116
139
|
icons: getToolIcons("text", readOnly("Text Rank")),
|
|
117
140
|
handler: async (params, _context) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
try {
|
|
142
|
+
const parsed = TextRankSchema.parse(params);
|
|
143
|
+
const cfg = sanitizeFtsConfig(parsed.config ?? "english");
|
|
144
|
+
const norm = parsed.normalization ?? 0;
|
|
145
|
+
// Handle both column (string) and columns (array) parameters
|
|
146
|
+
let cols;
|
|
147
|
+
if (parsed.columns !== undefined && parsed.columns.length > 0) {
|
|
148
|
+
cols = parsed.columns;
|
|
149
|
+
}
|
|
150
|
+
else if (parsed.column !== undefined) {
|
|
151
|
+
cols = [parsed.column];
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
error: "Either column or columns parameter is required",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// The preprocessor guarantees table is set (converts tableName → table)
|
|
160
|
+
const resolvedTable = parsed.table ?? parsed.tableName;
|
|
161
|
+
if (!resolvedTable) {
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
error: "Either 'table' or 'tableName' is required",
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
168
|
+
const sanitizedCols = sanitizeIdentifiers(cols);
|
|
169
|
+
const selectCols = parsed.select !== undefined && parsed.select.length > 0
|
|
170
|
+
? sanitizeIdentifiers(parsed.select).join(", ")
|
|
171
|
+
: "*";
|
|
172
|
+
const tsvector = sanitizedCols
|
|
173
|
+
.map((c) => `coalesce(${c}, '')`)
|
|
174
|
+
.join(" || ' ' || ");
|
|
175
|
+
const limitClause = parsed.limit !== undefined && parsed.limit > 0
|
|
176
|
+
? ` LIMIT ${String(parsed.limit)}`
|
|
177
|
+
: "";
|
|
178
|
+
const sql = `SELECT ${selectCols}, ts_rank_cd(to_tsvector('${cfg}', ${tsvector}), plainto_tsquery('${cfg}', $1), ${String(norm)}) as rank
|
|
149
179
|
FROM ${tableName}
|
|
150
180
|
WHERE to_tsvector('${cfg}', ${tsvector}) @@ plainto_tsquery('${cfg}', $1)
|
|
151
181
|
ORDER BY rank DESC${limitClause}`;
|
|
152
|
-
|
|
153
|
-
|
|
182
|
+
const result = await adapter.executeQuery(sql, [parsed.query]);
|
|
183
|
+
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (error instanceof ZodError) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: `pg_text_rank validation error: ${error.issues.map((e) => e.message).join(", ")}`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error: formatPostgresError(error, {
|
|
195
|
+
tool: "pg_text_rank",
|
|
196
|
+
}),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
154
199
|
},
|
|
155
200
|
};
|
|
156
201
|
}
|
|
@@ -164,29 +209,48 @@ function createTrigramSimilarityTool(adapter) {
|
|
|
164
209
|
annotations: readOnly("Trigram Similarity"),
|
|
165
210
|
icons: getToolIcons("text", readOnly("Trigram Similarity")),
|
|
166
211
|
handler: async (params, _context) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
212
|
+
try {
|
|
213
|
+
const parsed = TrigramSimilaritySchema.parse(params);
|
|
214
|
+
const thresh = parsed.threshold ?? 0.3;
|
|
215
|
+
// Default limit to 100 to prevent large payloads
|
|
216
|
+
const limitVal = parsed.limit !== undefined && parsed.limit > 0 ? parsed.limit : 100;
|
|
217
|
+
// The preprocessor guarantees table is set (converts tableName → table)
|
|
218
|
+
const resolvedTable = parsed.table ?? parsed.tableName;
|
|
219
|
+
if (!resolvedTable) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error: "Either 'table' or 'tableName' is required",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
226
|
+
const columnName = sanitizeIdentifier(parsed.column);
|
|
227
|
+
const selectCols = parsed.select !== undefined && parsed.select.length > 0
|
|
228
|
+
? sanitizeIdentifiers(parsed.select).join(", ")
|
|
229
|
+
: "*";
|
|
230
|
+
const additionalWhere = parsed.where
|
|
231
|
+
? ` AND (${sanitizeWhereClause(parsed.where)})`
|
|
232
|
+
: "";
|
|
233
|
+
const sql = `SELECT ${selectCols}, similarity(${columnName}, $1) as similarity
|
|
185
234
|
FROM ${tableName}
|
|
186
235
|
WHERE similarity(${columnName}, $1) > ${String(thresh)}${additionalWhere}
|
|
187
236
|
ORDER BY similarity DESC LIMIT ${String(limitVal)}`;
|
|
188
|
-
|
|
189
|
-
|
|
237
|
+
const result = await adapter.executeQuery(sql, [parsed.value]);
|
|
238
|
+
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (error instanceof ZodError) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
error: `pg_trigram_similarity validation error: ${error.issues.map((e) => e.message).join(", ")}`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
error: formatPostgresError(error, {
|
|
250
|
+
tool: "pg_trigram_similarity",
|
|
251
|
+
}),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
190
254
|
},
|
|
191
255
|
};
|
|
192
256
|
}
|
|
@@ -198,7 +262,10 @@ function createFuzzyMatchTool(adapter) {
|
|
|
198
262
|
tableName: z.string().optional().describe("Table name (alias for table)"),
|
|
199
263
|
column: z.string(),
|
|
200
264
|
value: z.string(),
|
|
201
|
-
method: z
|
|
265
|
+
method: z
|
|
266
|
+
.string()
|
|
267
|
+
.optional()
|
|
268
|
+
.describe("Fuzzy match method (default: levenshtein). Valid: soundex, levenshtein, metaphone"),
|
|
202
269
|
maxDistance: z
|
|
203
270
|
.number()
|
|
204
271
|
.optional()
|
|
@@ -225,37 +292,68 @@ function createFuzzyMatchTool(adapter) {
|
|
|
225
292
|
annotations: readOnly("Fuzzy Match"),
|
|
226
293
|
icons: getToolIcons("text", readOnly("Fuzzy Match")),
|
|
227
294
|
handler: async (params, _context) => {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
295
|
+
try {
|
|
296
|
+
const parsed = FuzzyMatchSchema.parse(params);
|
|
297
|
+
// Validate method (moved from z.enum to handler for structured error)
|
|
298
|
+
const VALID_METHODS = [
|
|
299
|
+
"levenshtein",
|
|
300
|
+
"soundex",
|
|
301
|
+
"metaphone",
|
|
302
|
+
];
|
|
303
|
+
const rawMethod = parsed.method ?? "levenshtein";
|
|
304
|
+
if (!VALID_METHODS.includes(rawMethod)) {
|
|
305
|
+
return {
|
|
306
|
+
success: false,
|
|
307
|
+
error: `Invalid method "${rawMethod}". Valid methods: ${VALID_METHODS.join(", ")}`,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
const method = rawMethod;
|
|
311
|
+
const maxDist = parsed.maxDistance ?? 3;
|
|
312
|
+
// Default limit to 100 to prevent large payloads
|
|
313
|
+
const limitVal = parsed.limit !== undefined && parsed.limit > 0 ? parsed.limit : 100;
|
|
314
|
+
// The preprocessor guarantees table is set (converts tableName → table)
|
|
315
|
+
const resolvedTable = parsed.table ?? parsed.tableName;
|
|
316
|
+
if (!resolvedTable) {
|
|
317
|
+
return {
|
|
318
|
+
success: false,
|
|
319
|
+
error: "Either 'table' or 'tableName' is required",
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
323
|
+
const columnName = sanitizeIdentifier(parsed.column);
|
|
324
|
+
const selectCols = parsed.select !== undefined && parsed.select.length > 0
|
|
325
|
+
? sanitizeIdentifiers(parsed.select).join(", ")
|
|
326
|
+
: "*";
|
|
327
|
+
const additionalWhere = parsed.where
|
|
328
|
+
? ` AND (${sanitizeWhereClause(parsed.where)})`
|
|
329
|
+
: "";
|
|
330
|
+
let sql;
|
|
331
|
+
if (method === "soundex") {
|
|
332
|
+
sql = `SELECT ${selectCols}, soundex(${columnName}) as code FROM ${tableName} WHERE soundex(${columnName}) = soundex($1)${additionalWhere} LIMIT ${String(limitVal)}`;
|
|
333
|
+
}
|
|
334
|
+
else if (method === "metaphone") {
|
|
335
|
+
sql = `SELECT ${selectCols}, metaphone(${columnName}, 10) as code FROM ${tableName} WHERE metaphone(${columnName}, 10) = metaphone($1, 10)${additionalWhere} LIMIT ${String(limitVal)}`;
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
sql = `SELECT ${selectCols}, levenshtein(${columnName}, $1) as distance FROM ${tableName} WHERE levenshtein(${columnName}, $1) <= ${String(maxDist)}${additionalWhere} ORDER BY distance LIMIT ${String(limitVal)}`;
|
|
339
|
+
}
|
|
340
|
+
const result = await adapter.executeQuery(sql, [parsed.value]);
|
|
341
|
+
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
238
342
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
343
|
+
catch (error) {
|
|
344
|
+
if (error instanceof ZodError) {
|
|
345
|
+
return {
|
|
346
|
+
success: false,
|
|
347
|
+
error: `pg_fuzzy_match validation error: ${error.issues.map((e) => e.message).join(", ")}`,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
success: false,
|
|
352
|
+
error: formatPostgresError(error, {
|
|
353
|
+
tool: "pg_fuzzy_match",
|
|
354
|
+
}),
|
|
355
|
+
};
|
|
250
356
|
}
|
|
251
|
-
else if (method === "metaphone") {
|
|
252
|
-
sql = `SELECT ${selectCols}, metaphone(${columnName}, 10) as code FROM ${tableName} WHERE metaphone(${columnName}, 10) = metaphone($1, 10)${additionalWhere} LIMIT ${String(limitVal)}`;
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
sql = `SELECT ${selectCols}, levenshtein(${columnName}, $1) as distance FROM ${tableName} WHERE levenshtein(${columnName}, $1) <= ${String(maxDist)}${additionalWhere} ORDER BY distance LIMIT ${String(limitVal)}`;
|
|
256
|
-
}
|
|
257
|
-
const result = await adapter.executeQuery(sql, [parsed.value]);
|
|
258
|
-
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
259
357
|
},
|
|
260
358
|
};
|
|
261
359
|
}
|
|
@@ -269,25 +367,46 @@ function createRegexpMatchTool(adapter) {
|
|
|
269
367
|
annotations: readOnly("Regexp Match"),
|
|
270
368
|
icons: getToolIcons("text", readOnly("Regexp Match")),
|
|
271
369
|
handler: async (params, _context) => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
370
|
+
try {
|
|
371
|
+
const parsed = RegexpMatchSchema.parse(params);
|
|
372
|
+
// The preprocessor guarantees table is set (converts tableName → table)
|
|
373
|
+
const resolvedTable = parsed.table ?? parsed.tableName;
|
|
374
|
+
if (!resolvedTable) {
|
|
375
|
+
return {
|
|
376
|
+
success: false,
|
|
377
|
+
error: "Either 'table' or 'tableName' is required",
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
381
|
+
const columnName = sanitizeIdentifier(parsed.column);
|
|
382
|
+
const selectCols = parsed.select !== undefined && parsed.select.length > 0
|
|
383
|
+
? sanitizeIdentifiers(parsed.select).join(", ")
|
|
384
|
+
: "*";
|
|
385
|
+
const op = parsed.flags?.includes("i") ? "~*" : "~";
|
|
386
|
+
const additionalWhere = parsed.where
|
|
387
|
+
? ` AND (${sanitizeWhereClause(parsed.where)})`
|
|
388
|
+
: "";
|
|
389
|
+
// Default limit to 100 to prevent large payloads
|
|
390
|
+
const limitVal = parsed.limit !== undefined && parsed.limit > 0 ? parsed.limit : 100;
|
|
391
|
+
const limitClause = ` LIMIT ${String(limitVal)}`;
|
|
392
|
+
const sql = `SELECT ${selectCols} FROM ${tableName} WHERE ${columnName} ${op} $1${additionalWhere}${limitClause}`;
|
|
393
|
+
const result = await adapter.executeQuery(sql, [parsed.pattern]);
|
|
394
|
+
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
if (error instanceof ZodError) {
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
error: `pg_regexp_match validation error: ${error.issues.map((e) => e.message).join(", ")}`,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
success: false,
|
|
405
|
+
error: formatPostgresError(error, {
|
|
406
|
+
tool: "pg_regexp_match",
|
|
407
|
+
}),
|
|
408
|
+
};
|
|
277
409
|
}
|
|
278
|
-
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
279
|
-
const columnName = sanitizeIdentifier(parsed.column);
|
|
280
|
-
const selectCols = parsed.select !== undefined && parsed.select.length > 0
|
|
281
|
-
? sanitizeIdentifiers(parsed.select).join(", ")
|
|
282
|
-
: "*";
|
|
283
|
-
const op = parsed.flags?.includes("i") ? "~*" : "~";
|
|
284
|
-
const additionalWhere = parsed.where
|
|
285
|
-
? ` AND (${sanitizeWhereClause(parsed.where)})`
|
|
286
|
-
: "";
|
|
287
|
-
const limitClause = parsed.limit !== undefined ? ` LIMIT ${String(parsed.limit)}` : "";
|
|
288
|
-
const sql = `SELECT ${selectCols} FROM ${tableName} WHERE ${columnName} ${op} $1${additionalWhere}${limitClause}`;
|
|
289
|
-
const result = await adapter.executeQuery(sql, [parsed.pattern]);
|
|
290
|
-
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
291
410
|
},
|
|
292
411
|
};
|
|
293
412
|
}
|
|
@@ -304,7 +423,10 @@ function createLikeSearchTool(adapter) {
|
|
|
304
423
|
.optional()
|
|
305
424
|
.describe("Use case-sensitive LIKE (default: false, uses ILIKE)"),
|
|
306
425
|
select: z.array(z.string()).optional(),
|
|
307
|
-
limit: z
|
|
426
|
+
limit: z
|
|
427
|
+
.number()
|
|
428
|
+
.optional()
|
|
429
|
+
.describe("Max results (default: 100 to prevent large payloads)"),
|
|
308
430
|
where: z.string().optional().describe("Additional WHERE clause filter"),
|
|
309
431
|
schema: z.string().optional().describe("Schema name (default: public)"),
|
|
310
432
|
})
|
|
@@ -322,27 +444,46 @@ function createLikeSearchTool(adapter) {
|
|
|
322
444
|
annotations: readOnly("LIKE Search"),
|
|
323
445
|
icons: getToolIcons("text", readOnly("LIKE Search")),
|
|
324
446
|
handler: async (params, _context) => {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
447
|
+
try {
|
|
448
|
+
const parsed = LikeSearchSchema.parse(params);
|
|
449
|
+
// The preprocessor guarantees table is set (converts tableName → table)
|
|
450
|
+
const resolvedTable = parsed.table ?? parsed.tableName;
|
|
451
|
+
if (!resolvedTable) {
|
|
452
|
+
return {
|
|
453
|
+
success: false,
|
|
454
|
+
error: "Either 'table' or 'tableName' is required",
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
458
|
+
const columnName = sanitizeIdentifier(parsed.column);
|
|
459
|
+
const selectCols = parsed.select !== undefined && parsed.select.length > 0
|
|
460
|
+
? sanitizeIdentifiers(parsed.select).join(", ")
|
|
461
|
+
: "*";
|
|
462
|
+
const op = parsed.caseSensitive === true ? "LIKE" : "ILIKE";
|
|
463
|
+
const additionalWhere = parsed.where
|
|
464
|
+
? ` AND (${sanitizeWhereClause(parsed.where)})`
|
|
465
|
+
: "";
|
|
466
|
+
// Default limit to 100 to prevent large payloads
|
|
467
|
+
const limitVal = parsed.limit !== undefined && parsed.limit > 0 ? parsed.limit : 100;
|
|
468
|
+
const limitClause = ` LIMIT ${String(limitVal)}`;
|
|
469
|
+
const sql = `SELECT ${selectCols} FROM ${tableName} WHERE ${columnName} ${op} $1${additionalWhere}${limitClause}`;
|
|
470
|
+
const result = await adapter.executeQuery(sql, [parsed.pattern]);
|
|
471
|
+
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
if (error instanceof ZodError) {
|
|
475
|
+
return {
|
|
476
|
+
success: false,
|
|
477
|
+
error: `pg_like_search validation error: ${error.issues.map((e) => e.message).join(", ")}`,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
success: false,
|
|
482
|
+
error: formatPostgresError(error, {
|
|
483
|
+
tool: "pg_like_search",
|
|
484
|
+
}),
|
|
485
|
+
};
|
|
330
486
|
}
|
|
331
|
-
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
332
|
-
const columnName = sanitizeIdentifier(parsed.column);
|
|
333
|
-
const selectCols = parsed.select !== undefined && parsed.select.length > 0
|
|
334
|
-
? sanitizeIdentifiers(parsed.select).join(", ")
|
|
335
|
-
: "*";
|
|
336
|
-
const op = parsed.caseSensitive === true ? "LIKE" : "ILIKE";
|
|
337
|
-
const additionalWhere = parsed.where
|
|
338
|
-
? ` AND (${sanitizeWhereClause(parsed.where)})`
|
|
339
|
-
: "";
|
|
340
|
-
const limitClause = parsed.limit !== undefined && parsed.limit > 0
|
|
341
|
-
? ` LIMIT ${String(parsed.limit)}`
|
|
342
|
-
: "";
|
|
343
|
-
const sql = `SELECT ${selectCols} FROM ${tableName} WHERE ${columnName} ${op} $1${additionalWhere}${limitClause}`;
|
|
344
|
-
const result = await adapter.executeQuery(sql, [parsed.pattern]);
|
|
345
|
-
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
346
487
|
},
|
|
347
488
|
};
|
|
348
489
|
}
|
|
@@ -390,40 +531,59 @@ function createTextHeadlineTool(adapter) {
|
|
|
390
531
|
annotations: readOnly("Text Headline"),
|
|
391
532
|
icons: getToolIcons("text", readOnly("Text Headline")),
|
|
392
533
|
handler: async (params, _context) => {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
534
|
+
try {
|
|
535
|
+
const parsed = HeadlineSchema.parse(params);
|
|
536
|
+
const cfg = sanitizeFtsConfig(parsed.config ?? "english");
|
|
537
|
+
// Build options string from individual params or use provided options
|
|
538
|
+
let opts;
|
|
539
|
+
if (parsed.options) {
|
|
540
|
+
opts = parsed.options;
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
const optParts = [];
|
|
544
|
+
optParts.push(`StartSel=${parsed.startSel ?? "<b>"}`);
|
|
545
|
+
optParts.push(`StopSel=${parsed.stopSel ?? "</b>"}`);
|
|
546
|
+
optParts.push(`MaxWords=${String(parsed.maxWords ?? 35)}`);
|
|
547
|
+
optParts.push(`MinWords=${String(parsed.minWords ?? 15)}`);
|
|
548
|
+
opts = optParts.join(", ");
|
|
549
|
+
}
|
|
550
|
+
// The preprocessor guarantees table is set (converts tableName → table)
|
|
551
|
+
const resolvedTable = parsed.table ?? parsed.tableName;
|
|
552
|
+
if (!resolvedTable) {
|
|
553
|
+
return {
|
|
554
|
+
success: false,
|
|
555
|
+
error: "Either 'table' or 'tableName' is required",
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
559
|
+
const columnName = sanitizeIdentifier(parsed.column);
|
|
560
|
+
// Use provided select columns, or default to * (user should specify PK for stable identification)
|
|
561
|
+
const selectCols = parsed.select !== undefined && parsed.select.length > 0
|
|
562
|
+
? sanitizeIdentifiers(parsed.select).join(", ") + ", "
|
|
563
|
+
: "";
|
|
564
|
+
const limitClause = parsed.limit !== undefined && parsed.limit > 0
|
|
565
|
+
? ` LIMIT ${String(parsed.limit)}`
|
|
566
|
+
: "";
|
|
567
|
+
const sql = `SELECT ${selectCols}ts_headline('${cfg}', ${columnName}, plainto_tsquery('${cfg}', $1), '${opts}') as headline
|
|
423
568
|
FROM ${tableName}
|
|
424
569
|
WHERE to_tsvector('${cfg}', ${columnName}) @@ plainto_tsquery('${cfg}', $1)${limitClause}`;
|
|
425
|
-
|
|
426
|
-
|
|
570
|
+
const result = await adapter.executeQuery(sql, [parsed.query]);
|
|
571
|
+
return { rows: result.rows, count: result.rows?.length ?? 0 };
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
if (error instanceof ZodError) {
|
|
575
|
+
return {
|
|
576
|
+
success: false,
|
|
577
|
+
error: `pg_text_headline validation error: ${error.issues.map((e) => e.message).join(", ")}`,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
success: false,
|
|
582
|
+
error: formatPostgresError(error, {
|
|
583
|
+
tool: "pg_text_headline",
|
|
584
|
+
}),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
427
587
|
},
|
|
428
588
|
};
|
|
429
589
|
}
|
|
@@ -456,36 +616,55 @@ function createFtsIndexTool(adapter) {
|
|
|
456
616
|
annotations: write("Create FTS Index"),
|
|
457
617
|
icons: getToolIcons("text", write("Create FTS Index")),
|
|
458
618
|
handler: async (params, _context) => {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
619
|
+
try {
|
|
620
|
+
const parsed = FtsIndexSchema.parse(params);
|
|
621
|
+
const cfg = sanitizeFtsConfig(parsed.config ?? "english");
|
|
622
|
+
// The preprocessor guarantees table is set (converts tableName → table)
|
|
623
|
+
const resolvedTable = parsed.table ?? parsed.tableName;
|
|
624
|
+
if (!resolvedTable) {
|
|
625
|
+
return {
|
|
626
|
+
success: false,
|
|
627
|
+
error: "Either 'table' or 'tableName' is required",
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
const defaultIndexName = `idx_${resolvedTable}_${parsed.column}_fts`;
|
|
631
|
+
const resolvedIndexName = parsed.name ?? defaultIndexName;
|
|
632
|
+
const indexName = sanitizeIdentifier(resolvedIndexName);
|
|
633
|
+
// Default to IF NOT EXISTS for safer operation (skip existing indexes)
|
|
634
|
+
const useIfNotExists = parsed.ifNotExists !== false;
|
|
635
|
+
const ifNotExists = useIfNotExists ? "IF NOT EXISTS " : "";
|
|
636
|
+
// Build qualified table name with schema support
|
|
637
|
+
const tableName = sanitizeTableName(resolvedTable, parsed.schema);
|
|
638
|
+
const columnName = sanitizeIdentifier(parsed.column);
|
|
639
|
+
// Check if index exists before creation (to accurately report 'skipped')
|
|
640
|
+
let existedBefore = false;
|
|
641
|
+
if (useIfNotExists) {
|
|
642
|
+
const checkResult = await adapter.executeQuery(`SELECT 1 FROM pg_indexes WHERE indexname = $1 LIMIT 1`, [resolvedIndexName]);
|
|
643
|
+
existedBefore = (checkResult.rows?.length ?? 0) > 0;
|
|
644
|
+
}
|
|
645
|
+
const sql = `CREATE INDEX ${ifNotExists}${indexName} ON ${tableName} USING gin(to_tsvector('${cfg}', ${columnName}))`;
|
|
646
|
+
await adapter.executeQuery(sql);
|
|
647
|
+
return {
|
|
648
|
+
success: true,
|
|
649
|
+
index: resolvedIndexName,
|
|
650
|
+
config: cfg,
|
|
651
|
+
skipped: existedBefore,
|
|
652
|
+
};
|
|
465
653
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
existedBefore = (checkResult.rows?.length ?? 0) > 0;
|
|
654
|
+
catch (error) {
|
|
655
|
+
if (error instanceof ZodError) {
|
|
656
|
+
return {
|
|
657
|
+
success: false,
|
|
658
|
+
error: `pg_create_fts_index validation error: ${error.issues.map((e) => e.message).join(", ")}`,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
success: false,
|
|
663
|
+
error: formatPostgresError(error, {
|
|
664
|
+
tool: "pg_create_fts_index",
|
|
665
|
+
}),
|
|
666
|
+
};
|
|
480
667
|
}
|
|
481
|
-
const sql = `CREATE INDEX ${ifNotExists}${indexName} ON ${tableName} USING gin(to_tsvector('${cfg}', ${columnName}))`;
|
|
482
|
-
await adapter.executeQuery(sql);
|
|
483
|
-
return {
|
|
484
|
-
success: true,
|
|
485
|
-
index: resolvedIndexName,
|
|
486
|
-
config: cfg,
|
|
487
|
-
skipped: existedBefore,
|
|
488
|
-
};
|
|
489
668
|
},
|
|
490
669
|
};
|
|
491
670
|
}
|
|
@@ -529,8 +708,7 @@ function createTextSentimentTool(_adapter) {
|
|
|
529
708
|
outputSchema: TextSentimentOutputSchema,
|
|
530
709
|
annotations: readOnly("Text Sentiment"),
|
|
531
710
|
icons: getToolIcons("text", readOnly("Text Sentiment")),
|
|
532
|
-
|
|
533
|
-
handler: async (params, _context) => {
|
|
711
|
+
handler: (params, _context) => {
|
|
534
712
|
const parsed = SentimentSchema.parse(params ?? {});
|
|
535
713
|
const text = parsed.text.toLowerCase();
|
|
536
714
|
const positiveWords = [
|
|
@@ -619,7 +797,7 @@ function createTextSentimentTool(_adapter) {
|
|
|
619
797
|
result.matchedPositive = matchedPositive;
|
|
620
798
|
result.matchedNegative = matchedNegative;
|
|
621
799
|
}
|
|
622
|
-
return result;
|
|
800
|
+
return Promise.resolve(result);
|
|
623
801
|
},
|
|
624
802
|
};
|
|
625
803
|
}
|