db-mcp 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitleaks.toml +9 -0
- package/.trivyignore +8 -0
- package/README.md +193 -120
- package/dist/{chunk-DWJXQEFA.js → chunk-5Y42NPBP.js} +4168 -2411
- package/dist/{chunk-AOUL5SHS.js → chunk-645ZEFLA.js} +70 -20
- package/dist/chunk-OKOVZ5QE.js +28 -0
- package/dist/chunk-SFJQCNG7.js +131 -0
- package/dist/{chunk-S5IDDSSB.js → chunk-VIDSICEL.js} +12 -1
- package/dist/chunk-WBER5YY4.js +2053 -0
- package/dist/{chunk-4IA3DB5C.js → chunk-X3MUUOWM.js} +19 -2
- package/dist/{chunk-Z2GFQU3G.js → chunk-Z7C2TM4L.js} +114 -21
- package/dist/cli.js +79 -5
- package/dist/{http-VSB7DBJR.js → http-6KF4ULDI.js} +199 -77
- package/dist/index.d.ts +231 -8
- package/dist/index.js +6 -5
- package/dist/{sqlite-VHBA4ABV.js → sqlite-U5KSYQXK.js} +63 -140
- package/dist/{sqlite-native-D2LH5ZT7.js → sqlite-native-JXMCFQBA.js} +576 -114
- package/dist/worker-script.js +34 -10
- package/logs/.gitkeep +1 -0
- package/mcp-config-example.json +83 -0
- package/package.json +9 -8
- package/playwright.config.ts +1 -1
- package/scripts/update-badges.ts +99 -0
- package/server.json +7 -5
- package/test-server/README.md +20 -23
- package/test-server/code-map.md +45 -33
- package/test-server/reset-database.ps1 +59 -17
- package/test-server/scripts/README.md +27 -0
- package/test-server/{test-help-resources.mjs → scripts/test-help-resources.mjs} +12 -5
- package/test-server/scripts/test-prompts.mjs +251 -0
- package/test-server/{test-tool-annotations.mjs → scripts/test-tool-annotations.mjs} +9 -4
- package/test-server/test-advanced/README.md +70 -0
- package/test-server/test-advanced/test-codemode-advanced-admin.md +174 -0
- package/test-server/test-advanced/test-codemode-advanced-core.md +193 -0
- package/test-server/test-advanced/test-codemode-advanced-geo.md +157 -0
- package/test-server/test-advanced/test-codemode-advanced-introspection.md +171 -0
- package/test-server/test-advanced/test-codemode-advanced-json.md +169 -0
- package/test-server/test-advanced/test-codemode-advanced-migration.md +160 -0
- package/test-server/test-advanced/test-codemode-advanced-stats.md +185 -0
- package/test-server/test-advanced/test-codemode-advanced-text.md +183 -0
- package/test-server/test-advanced/test-codemode-advanced-transactions.md +136 -0
- package/test-server/test-advanced/test-codemode-advanced-vector.md +141 -0
- package/test-server/test-codemode/README.md +121 -0
- package/test-server/test-codemode/test-codemode-admin.md +223 -0
- package/test-server/test-codemode/test-codemode-core.md +286 -0
- package/test-server/test-codemode/test-codemode-geo.md +177 -0
- package/test-server/test-codemode/test-codemode-introspection.md +190 -0
- package/test-server/test-codemode/test-codemode-json.md +237 -0
- package/test-server/test-codemode/test-codemode-migration.md +278 -0
- package/test-server/test-codemode/test-codemode-sandbox.md +413 -0
- package/test-server/test-codemode/test-codemode-stats.md +232 -0
- package/test-server/test-codemode/test-codemode-text.md +237 -0
- package/test-server/test-codemode/test-codemode-transactions.md +236 -0
- package/test-server/test-codemode/test-codemode-vector.md +244 -0
- package/test-server/test-codemode/test-codemode-wasm-degradation.md +394 -0
- package/test-server/test-database.sql +37 -1
- package/test-server/test-resources.md +43 -16
- package/test-server/test-tool-groups/README.md +100 -0
- package/test-server/test-tool-groups/test-admin-core.md +165 -0
- package/test-server/test-tool-groups/test-admin-extensions.md +133 -0
- package/test-server/{test-tools.md → test-tool-groups/test-core-data.md} +103 -17
- package/test-server/test-tool-groups/test-core-schema.md +240 -0
- package/test-server/test-tool-groups/test-geo-haversine.md +130 -0
- package/test-server/test-tool-groups/test-geo-spatialite.md +110 -0
- package/test-server/test-tool-groups/test-introspection-diagnostics.md +123 -0
- package/test-server/test-tool-groups/test-introspection-schema.md +133 -0
- package/test-server/test-tool-groups/test-json-read.md +219 -0
- package/test-server/test-tool-groups/test-json-write.md +157 -0
- package/test-server/test-tool-groups/test-migration.md +193 -0
- package/test-server/test-tool-groups/test-stats-advanced.md +118 -0
- package/test-server/test-tool-groups/test-stats-basic.md +156 -0
- package/test-server/test-tool-groups/test-text-advanced.md +169 -0
- package/test-server/test-tool-groups/test-text-basic.md +177 -0
- package/test-server/test-tool-groups/test-transactions.md +179 -0
- package/test-server/test-tool-groups/test-vector-read.md +130 -0
- package/test-server/test-tool-groups/test-vector-write.md +115 -0
- package/test-server/tool-reference.md +79 -58
- package/tsconfig.build.json +6 -0
- package/dist/chunk-DZQLDEQS.js +0 -879
- package/test-server/test-agent-experience.md +0 -243
- package/test-server/test-group-tools.md +0 -861
- package/test-server/test-tools-advanced-1.md +0 -517
- package/test-server/test-tools-advanced-2.md +0 -487
- package/test-server/test-tools-codemode.md +0 -629
|
@@ -1,41 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DatabaseAdapter } from './chunk-
|
|
3
|
-
import { write, readOnly, admin, getToolGroupIcon, idempotent } from './chunk-
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
1
|
+
import { ErrorResponseFields, RowRecordSchema, validateWhereClause, resolveAliases, autoEnableWal, detectAndSetJsonbSupport, SchemaManager, applyCommonPragmas, isDDL, getResourceDefinitions, getPromptDefinitions, normalizeSqliteParams, translateSqliteError, getCoreTools, getJsonOperationTools, getJsonHelperTools, getTextTools, getStatsTools, getVirtualTools, getVectorTools, getGeoTools, getAdminTools, getIntrospectionTools, getMigrationTools, getCodeModeTools, sanitizeIdentifier, validateTableExists, FtsHeadlineSchema } from './chunk-5Y42NPBP.js';
|
|
2
|
+
import { DatabaseAdapter } from './chunk-Z7C2TM4L.js';
|
|
3
|
+
import { write, readOnly, VERSION, admin, getToolGroupIcon, idempotent } from './chunk-VIDSICEL.js';
|
|
4
|
+
import './chunk-OKOVZ5QE.js';
|
|
5
|
+
import { formatHandlerError, ValidationError, ConfigurationError, ConnectionError, ResourceNotFoundError, ExtensionNotAvailableError } from './chunk-X3MUUOWM.js';
|
|
6
|
+
import { logger, ERROR_CODES, DbMcpError } from './chunk-645ZEFLA.js';
|
|
6
7
|
import Database from 'better-sqlite3';
|
|
7
8
|
import { z } from 'zod';
|
|
8
9
|
import path from 'path';
|
|
9
10
|
import fs from 'fs';
|
|
10
11
|
import { fileURLToPath } from 'url';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
13
|
+
var FtsCreateOutputSchema = z.object({
|
|
14
|
+
success: z.boolean(),
|
|
15
|
+
message: z.string().optional(),
|
|
16
|
+
tableName: z.string().optional(),
|
|
17
|
+
triggersCreated: z.array(z.string()).optional()
|
|
18
|
+
}).extend(ErrorResponseFields.shape);
|
|
19
|
+
var FtsResultSchema = z.object({
|
|
20
|
+
rank: z.number().nullable().optional(),
|
|
21
|
+
bm25: z.number().nullable().optional()
|
|
22
|
+
}).loose();
|
|
23
|
+
var FtsSearchOutputSchema = z.object({
|
|
24
|
+
success: z.boolean(),
|
|
25
|
+
rowCount: z.number().optional(),
|
|
26
|
+
results: z.array(FtsResultSchema).optional()
|
|
27
|
+
}).extend(ErrorResponseFields.shape);
|
|
28
|
+
var FtsRebuildOutputSchema = z.object({
|
|
29
|
+
success: z.boolean(),
|
|
30
|
+
message: z.string().optional(),
|
|
31
|
+
tableName: z.string().optional()
|
|
32
|
+
}).extend(ErrorResponseFields.shape);
|
|
33
|
+
var FtsHeadlineOutputSchema = z.object({
|
|
34
|
+
success: z.boolean(),
|
|
35
|
+
rowCount: z.number().optional(),
|
|
36
|
+
results: z.array(
|
|
37
|
+
z.object({
|
|
38
|
+
headline: z.string().optional(),
|
|
39
|
+
snippet: z.string().optional(),
|
|
40
|
+
rank: z.number().nullable().optional()
|
|
41
|
+
}).loose()
|
|
42
|
+
).optional()
|
|
43
|
+
}).extend(ErrorResponseFields.shape);
|
|
36
44
|
var coerceEnumValues = (allowed) => (val) => typeof val === "string" && allowed.includes(val) ? val : void 0;
|
|
37
45
|
var FtsCreateSchema = z.object({
|
|
38
|
-
tableName: z.string().describe("Name of the FTS table to create"),
|
|
46
|
+
tableName: z.string().optional().describe("Name of the FTS table to create"),
|
|
47
|
+
ftsTable: z.string().optional().describe("Name of the FTS table to create (alias)"),
|
|
39
48
|
sourceTable: z.string().describe("Source table to index"),
|
|
40
49
|
columns: z.array(z.string()).describe("Columns to include in the index"),
|
|
41
50
|
contentTable: z.string().optional().describe("Content table for external content FTS"),
|
|
@@ -43,9 +52,7 @@ var FtsCreateSchema = z.object({
|
|
|
43
52
|
coerceEnumValues(["unicode61", "ascii", "porter"]),
|
|
44
53
|
z.enum(["unicode61", "ascii", "porter"]).optional().default("unicode61")
|
|
45
54
|
),
|
|
46
|
-
createTriggers: z.boolean().optional().default(true).describe(
|
|
47
|
-
"Create INSERT/UPDATE/DELETE triggers for auto-sync (only for external content tables)"
|
|
48
|
-
)
|
|
55
|
+
createTriggers: z.boolean().optional().default(true).describe("Create triggers")
|
|
49
56
|
});
|
|
50
57
|
var FtsSearchSchema = z.object({
|
|
51
58
|
table: z.string().describe("FTS table name"),
|
|
@@ -68,12 +75,39 @@ var FtsMatchInfoSchema = z.object({
|
|
|
68
75
|
z.enum(["bm25", "rank"]).optional().default("bm25")
|
|
69
76
|
)
|
|
70
77
|
});
|
|
78
|
+
|
|
79
|
+
// src/adapters/sqlite/tools/fts.ts
|
|
80
|
+
function buildFts5UnavailableError(tableName) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
message: "FTS5 module unavailable",
|
|
84
|
+
tableName: tableName ?? "",
|
|
85
|
+
error: "FTS5 module unavailable",
|
|
86
|
+
hint: "FTS5 requires native SQLite backend (--sqlite-native). WASM mode (sql.js) does not include this module."
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function isFts5UnavailableError(error) {
|
|
90
|
+
return error instanceof Error && error.message.toLowerCase().includes("no such module: fts5");
|
|
91
|
+
}
|
|
92
|
+
function isFts5Available(adapter) {
|
|
93
|
+
return adapter.isNativeBackend();
|
|
94
|
+
}
|
|
95
|
+
function buildFts5SearchUnavailableError() {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
rowCount: 0,
|
|
99
|
+
results: [],
|
|
100
|
+
error: "FTS5 module unavailable",
|
|
101
|
+
hint: "FTS5 requires native SQLite backend (--sqlite-native). WASM mode (sql.js) does not include this module."
|
|
102
|
+
};
|
|
103
|
+
}
|
|
71
104
|
function getFtsTools(adapter) {
|
|
72
105
|
return [
|
|
73
106
|
createFtsCreateTool(adapter),
|
|
74
107
|
createFtsSearchTool(adapter),
|
|
75
108
|
createFtsRebuildTool(adapter),
|
|
76
|
-
createFtsMatchInfoTool(adapter)
|
|
109
|
+
createFtsMatchInfoTool(adapter),
|
|
110
|
+
createFtsHeadlineTool(adapter)
|
|
77
111
|
];
|
|
78
112
|
}
|
|
79
113
|
function createFtsCreateTool(adapter) {
|
|
@@ -88,7 +122,13 @@ function createFtsCreateTool(adapter) {
|
|
|
88
122
|
handler: async (params, _context) => {
|
|
89
123
|
try {
|
|
90
124
|
const input = FtsCreateSchema.parse(params);
|
|
91
|
-
|
|
125
|
+
const targetTableName = input.tableName || input.ftsTable;
|
|
126
|
+
if (!targetTableName) {
|
|
127
|
+
throw new ValidationError(
|
|
128
|
+
"Must provide either tableName or ftsTable"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
sanitizeIdentifier(targetTableName);
|
|
92
132
|
sanitizeIdentifier(input.sourceTable);
|
|
93
133
|
for (const col of input.columns) {
|
|
94
134
|
sanitizeIdentifier(col);
|
|
@@ -98,31 +138,31 @@ function createFtsCreateTool(adapter) {
|
|
|
98
138
|
const effectiveContentTable = input.contentTable ?? input.sourceTable;
|
|
99
139
|
sanitizeIdentifier(effectiveContentTable);
|
|
100
140
|
options += `, content="${effectiveContentTable}"`;
|
|
101
|
-
const sql = `CREATE VIRTUAL TABLE IF NOT EXISTS "${
|
|
141
|
+
const sql = `CREATE VIRTUAL TABLE IF NOT EXISTS "${targetTableName}" USING fts5(${columnList}, ${options})`;
|
|
102
142
|
await adapter.executeQuery(sql);
|
|
103
143
|
let triggersCreated = [];
|
|
104
144
|
if (input.createTriggers) {
|
|
105
145
|
triggersCreated = await createSyncTriggers(
|
|
106
146
|
adapter,
|
|
107
|
-
|
|
147
|
+
targetTableName,
|
|
108
148
|
effectiveContentTable,
|
|
109
149
|
input.columns
|
|
110
150
|
);
|
|
111
151
|
}
|
|
112
152
|
await adapter.executeQuery(
|
|
113
|
-
`INSERT INTO "${
|
|
153
|
+
`INSERT INTO "${targetTableName}"("${targetTableName}") VALUES('rebuild')`
|
|
114
154
|
);
|
|
115
|
-
const message = triggersCreated.length ? `FTS5 table '${
|
|
155
|
+
const message = triggersCreated.length ? `FTS5 table '${targetTableName}' created with ${triggersCreated.length} sync triggers` : `FTS5 table '${targetTableName}' created`;
|
|
116
156
|
return {
|
|
117
157
|
success: true,
|
|
118
158
|
message,
|
|
119
|
-
tableName:
|
|
159
|
+
tableName: targetTableName,
|
|
120
160
|
triggersCreated: triggersCreated.length ? triggersCreated : void 0
|
|
121
161
|
};
|
|
122
162
|
} catch (error) {
|
|
123
163
|
if (isFts5UnavailableError(error)) {
|
|
124
164
|
return buildFts5UnavailableError(
|
|
125
|
-
params?.tableName
|
|
165
|
+
params?.tableName || params?.ftsTable
|
|
126
166
|
);
|
|
127
167
|
}
|
|
128
168
|
return formatHandlerError(error);
|
|
@@ -292,6 +332,223 @@ function createFtsMatchInfoTool(adapter) {
|
|
|
292
332
|
}
|
|
293
333
|
};
|
|
294
334
|
}
|
|
335
|
+
function createFtsHeadlineTool(adapter) {
|
|
336
|
+
return {
|
|
337
|
+
name: "sqlite_fts_headline",
|
|
338
|
+
description: "Generate highlighted snippets from FTS5 full-text search results. Returns headline (highlight) and snippet (context window) for each match.",
|
|
339
|
+
group: "text",
|
|
340
|
+
inputSchema: FtsHeadlineSchema,
|
|
341
|
+
outputSchema: FtsHeadlineOutputSchema,
|
|
342
|
+
requiredScopes: ["read"],
|
|
343
|
+
annotations: readOnly("FTS Headline"),
|
|
344
|
+
handler: async (params, _context) => {
|
|
345
|
+
try {
|
|
346
|
+
const input = FtsHeadlineSchema.parse(params);
|
|
347
|
+
if (!isFts5Available(adapter)) {
|
|
348
|
+
return buildFts5SearchUnavailableError();
|
|
349
|
+
}
|
|
350
|
+
sanitizeIdentifier(input.table);
|
|
351
|
+
await validateTableExists(adapter, input.table);
|
|
352
|
+
const queryEscaped = input.query.replace(/'/g, "''");
|
|
353
|
+
let columnIndex = 0;
|
|
354
|
+
if (input.column) {
|
|
355
|
+
const colResult = await adapter.executeReadQuery(
|
|
356
|
+
`PRAGMA table_info("${input.table}")`
|
|
357
|
+
);
|
|
358
|
+
const colRows = colResult.rows ?? [];
|
|
359
|
+
const found = colRows.findIndex(
|
|
360
|
+
(r) => String(r["name"]) === input.column
|
|
361
|
+
);
|
|
362
|
+
if (found >= 0) {
|
|
363
|
+
columnIndex = found;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const startSel = input.startSel.replace(/'/g, "''");
|
|
367
|
+
const stopSel = input.stopSel.replace(/'/g, "''");
|
|
368
|
+
const highlightExpr = `highlight("${input.table}", ${String(columnIndex)}, '${startSel}', '${stopSel}')`;
|
|
369
|
+
const snippetExpr = `snippet("${input.table}", ${String(columnIndex)}, '${startSel}', '${stopSel}', '...', ${String(input.snippetWords)})`;
|
|
370
|
+
if (input.query === "*" || input.query.trim() === "") {
|
|
371
|
+
const sql2 = `SELECT *, NULL as headline, NULL as snippet, NULL as rank FROM "${input.table}" ORDER BY rowid LIMIT ${String(input.limit)}`;
|
|
372
|
+
const result2 = await adapter.executeReadQuery(sql2);
|
|
373
|
+
return {
|
|
374
|
+
success: true,
|
|
375
|
+
rowCount: result2.rows?.length ?? 0,
|
|
376
|
+
results: result2.rows
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const sql = `SELECT ${highlightExpr} as headline, ${snippetExpr} as snippet, rank FROM "${input.table}" WHERE "${input.table}" MATCH '${queryEscaped}' ORDER BY rank LIMIT ${String(input.limit)}`;
|
|
380
|
+
const result = await adapter.executeReadQuery(sql);
|
|
381
|
+
return {
|
|
382
|
+
success: true,
|
|
383
|
+
rowCount: result.rows?.length ?? 0,
|
|
384
|
+
results: result.rows
|
|
385
|
+
};
|
|
386
|
+
} catch (error) {
|
|
387
|
+
if (isFts5UnavailableError(error)) {
|
|
388
|
+
return buildFts5SearchUnavailableError();
|
|
389
|
+
}
|
|
390
|
+
return formatHandlerError(error);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
var TransactionBeginOutputSchema = z.object({
|
|
396
|
+
success: z.boolean(),
|
|
397
|
+
message: z.string().optional(),
|
|
398
|
+
mode: z.string().optional()
|
|
399
|
+
}).extend(ErrorResponseFields.shape);
|
|
400
|
+
var TransactionCommitOutputSchema = z.object({
|
|
401
|
+
success: z.boolean(),
|
|
402
|
+
message: z.string().optional()
|
|
403
|
+
}).extend(ErrorResponseFields.shape);
|
|
404
|
+
var TransactionRollbackOutputSchema = z.object({
|
|
405
|
+
success: z.boolean(),
|
|
406
|
+
message: z.string().optional()
|
|
407
|
+
}).extend(ErrorResponseFields.shape);
|
|
408
|
+
var TransactionSavepointOutputSchema = z.object({
|
|
409
|
+
success: z.boolean(),
|
|
410
|
+
message: z.string().optional(),
|
|
411
|
+
name: z.string().optional(),
|
|
412
|
+
savepoint: z.string().optional()
|
|
413
|
+
}).extend(ErrorResponseFields.shape);
|
|
414
|
+
var TransactionReleaseOutputSchema = z.object({
|
|
415
|
+
success: z.boolean(),
|
|
416
|
+
message: z.string().optional(),
|
|
417
|
+
name: z.string().optional(),
|
|
418
|
+
savepoint: z.string().optional()
|
|
419
|
+
}).extend(ErrorResponseFields.shape);
|
|
420
|
+
var TransactionRollbackToOutputSchema = z.object({
|
|
421
|
+
success: z.boolean(),
|
|
422
|
+
message: z.string().optional(),
|
|
423
|
+
name: z.string().optional(),
|
|
424
|
+
savepoint: z.string().optional()
|
|
425
|
+
}).extend(ErrorResponseFields.shape);
|
|
426
|
+
var TransactionExecuteOutputSchema = z.object({
|
|
427
|
+
success: z.boolean(),
|
|
428
|
+
message: z.string().optional(),
|
|
429
|
+
statementsExecuted: z.number().optional(),
|
|
430
|
+
results: z.array(z.unknown()).optional()
|
|
431
|
+
}).extend(ErrorResponseFields.shape);
|
|
432
|
+
var TransactionStatusOutputSchema = z.object({
|
|
433
|
+
success: z.boolean(),
|
|
434
|
+
status: z.enum(["active", "none"]).optional().describe(
|
|
435
|
+
'Transaction state: "active" (transaction in progress) or "none" (no active transaction)'
|
|
436
|
+
),
|
|
437
|
+
active: z.boolean().optional().describe("Whether a transaction is currently in progress"),
|
|
438
|
+
message: z.string().optional()
|
|
439
|
+
}).extend(ErrorResponseFields.shape);
|
|
440
|
+
var RowNumberResultSchema = z.object({
|
|
441
|
+
row_number: z.number()
|
|
442
|
+
}).loose();
|
|
443
|
+
var WindowRowNumberOutputSchema = z.object({
|
|
444
|
+
success: z.boolean(),
|
|
445
|
+
rowCount: z.number().optional(),
|
|
446
|
+
rows: z.array(RowNumberResultSchema).optional(),
|
|
447
|
+
hint: z.string().optional()
|
|
448
|
+
}).extend(ErrorResponseFields.shape);
|
|
449
|
+
var RankResultSchema = z.object({
|
|
450
|
+
rank: z.number().optional(),
|
|
451
|
+
dense_rank: z.number().optional(),
|
|
452
|
+
percent_rank: z.number().optional()
|
|
453
|
+
}).loose();
|
|
454
|
+
var WindowRankOutputSchema = z.object({
|
|
455
|
+
success: z.boolean(),
|
|
456
|
+
rankType: z.string().optional(),
|
|
457
|
+
rowCount: z.number().optional(),
|
|
458
|
+
rows: z.array(RankResultSchema).optional(),
|
|
459
|
+
hint: z.string().optional()
|
|
460
|
+
}).extend(ErrorResponseFields.shape);
|
|
461
|
+
var LagLeadResultSchema = z.object({
|
|
462
|
+
lag_value: z.unknown().optional(),
|
|
463
|
+
lead_value: z.unknown().optional()
|
|
464
|
+
}).loose();
|
|
465
|
+
var WindowLagLeadOutputSchema = z.object({
|
|
466
|
+
success: z.boolean(),
|
|
467
|
+
direction: z.string().optional(),
|
|
468
|
+
offset: z.number().optional(),
|
|
469
|
+
rowCount: z.number().optional(),
|
|
470
|
+
rows: z.array(LagLeadResultSchema).optional(),
|
|
471
|
+
hint: z.string().optional()
|
|
472
|
+
}).extend(ErrorResponseFields.shape);
|
|
473
|
+
var RunningTotalResultSchema = z.object({
|
|
474
|
+
running_total: z.number()
|
|
475
|
+
}).loose();
|
|
476
|
+
var WindowRunningTotalOutputSchema = z.object({
|
|
477
|
+
success: z.boolean(),
|
|
478
|
+
valueColumn: z.string().optional(),
|
|
479
|
+
rowCount: z.number().optional(),
|
|
480
|
+
rows: z.array(RunningTotalResultSchema).optional(),
|
|
481
|
+
hint: z.string().optional()
|
|
482
|
+
}).extend(ErrorResponseFields.shape);
|
|
483
|
+
var MovingAvgResultSchema = z.object({
|
|
484
|
+
moving_avg: z.number()
|
|
485
|
+
}).loose();
|
|
486
|
+
var WindowMovingAvgOutputSchema = z.object({
|
|
487
|
+
success: z.boolean(),
|
|
488
|
+
valueColumn: z.string().optional(),
|
|
489
|
+
windowSize: z.number().optional(),
|
|
490
|
+
rowCount: z.number().optional(),
|
|
491
|
+
rows: z.array(MovingAvgResultSchema).optional(),
|
|
492
|
+
hint: z.string().optional()
|
|
493
|
+
}).extend(ErrorResponseFields.shape);
|
|
494
|
+
var NtileResultSchema = z.object({
|
|
495
|
+
ntile: z.number()
|
|
496
|
+
}).loose();
|
|
497
|
+
var WindowNtileOutputSchema = z.object({
|
|
498
|
+
success: z.boolean(),
|
|
499
|
+
buckets: z.number().optional(),
|
|
500
|
+
rowCount: z.number().optional(),
|
|
501
|
+
rows: z.array(NtileResultSchema).optional(),
|
|
502
|
+
hint: z.string().optional()
|
|
503
|
+
}).extend(ErrorResponseFields.shape);
|
|
504
|
+
var SpatialiteLoadOutputSchema = z.object({
|
|
505
|
+
success: z.boolean(),
|
|
506
|
+
message: z.string().optional(),
|
|
507
|
+
alreadyLoaded: z.boolean().optional(),
|
|
508
|
+
extensionPath: z.string().optional(),
|
|
509
|
+
searchedPaths: z.array(z.string()).optional()
|
|
510
|
+
}).extend(ErrorResponseFields.shape);
|
|
511
|
+
var SpatialiteCreateTableOutputSchema = z.object({
|
|
512
|
+
success: z.boolean(),
|
|
513
|
+
message: z.string().optional(),
|
|
514
|
+
tableName: z.string().optional(),
|
|
515
|
+
alreadyExists: z.boolean().optional(),
|
|
516
|
+
geometryColumn: z.string().optional(),
|
|
517
|
+
geometryType: z.string().optional(),
|
|
518
|
+
srid: z.number().optional()
|
|
519
|
+
}).extend(ErrorResponseFields.shape);
|
|
520
|
+
var SpatialiteQueryOutputSchema = z.object({
|
|
521
|
+
success: z.boolean(),
|
|
522
|
+
rowCount: z.number().optional(),
|
|
523
|
+
rows: z.array(RowRecordSchema).optional()
|
|
524
|
+
}).extend(ErrorResponseFields.shape);
|
|
525
|
+
var SpatialiteIndexOutputSchema = z.object({
|
|
526
|
+
success: z.boolean(),
|
|
527
|
+
message: z.string().optional(),
|
|
528
|
+
action: z.string().optional(),
|
|
529
|
+
alreadyExists: z.boolean().optional(),
|
|
530
|
+
alreadyDropped: z.boolean().optional(),
|
|
531
|
+
indexed: z.boolean().optional(),
|
|
532
|
+
valid: z.boolean().nullable().optional()
|
|
533
|
+
}).extend(ErrorResponseFields.shape);
|
|
534
|
+
var SpatialiteAnalyzeOutputSchema = z.object({
|
|
535
|
+
success: z.boolean(),
|
|
536
|
+
analysisType: z.string().optional(),
|
|
537
|
+
rowCount: z.number().optional(),
|
|
538
|
+
results: z.array(RowRecordSchema).optional()
|
|
539
|
+
}).extend(ErrorResponseFields.shape);
|
|
540
|
+
var SpatialiteTransformOutputSchema = z.object({
|
|
541
|
+
success: z.boolean(),
|
|
542
|
+
operation: z.string().optional(),
|
|
543
|
+
result: z.string().optional()
|
|
544
|
+
}).extend(ErrorResponseFields.shape);
|
|
545
|
+
var SpatialiteImportOutputSchema = z.object({
|
|
546
|
+
success: z.boolean(),
|
|
547
|
+
message: z.string().optional(),
|
|
548
|
+
rowsAffected: z.number().optional()
|
|
549
|
+
}).extend(ErrorResponseFields.shape);
|
|
550
|
+
|
|
551
|
+
// src/adapters/sqlite-native/tools/transactions.ts
|
|
295
552
|
var VALID_MODES = ["deferred", "immediate", "exclusive"];
|
|
296
553
|
var BeginTransactionSchema = z.object({
|
|
297
554
|
mode: z.preprocess(
|
|
@@ -315,6 +572,7 @@ var ExecuteInTransactionSchema = z.object({
|
|
|
315
572
|
function getTransactionTools(adapter) {
|
|
316
573
|
return [
|
|
317
574
|
createBeginTransactionTool(adapter),
|
|
575
|
+
createTransactionStatusTool(adapter),
|
|
318
576
|
createCommitTransactionTool(adapter),
|
|
319
577
|
createRollbackTransactionTool(adapter),
|
|
320
578
|
createSavepointTool(adapter),
|
|
@@ -327,7 +585,7 @@ function createBeginTransactionTool(adapter) {
|
|
|
327
585
|
return {
|
|
328
586
|
name: "sqlite_transaction_begin",
|
|
329
587
|
description: "Begin a new transaction. Use immediate or exclusive mode for write-heavy operations.",
|
|
330
|
-
group: "
|
|
588
|
+
group: "transactions",
|
|
331
589
|
inputSchema: BeginTransactionSchema,
|
|
332
590
|
outputSchema: TransactionBeginOutputSchema,
|
|
333
591
|
annotations: write("Begin Transaction"),
|
|
@@ -348,11 +606,36 @@ function createBeginTransactionTool(adapter) {
|
|
|
348
606
|
}
|
|
349
607
|
};
|
|
350
608
|
}
|
|
609
|
+
function createTransactionStatusTool(adapter) {
|
|
610
|
+
return {
|
|
611
|
+
name: "sqlite_transaction_status",
|
|
612
|
+
description: "Check whether a transaction is currently active. Returns status and a boolean flag. Read-only \u2014 does not alter transaction state.",
|
|
613
|
+
group: "transactions",
|
|
614
|
+
inputSchema: z.object({}),
|
|
615
|
+
outputSchema: TransactionStatusOutputSchema,
|
|
616
|
+
annotations: readOnly("Transaction Status"),
|
|
617
|
+
requiredScopes: ["read"],
|
|
618
|
+
handler: (_params, _context) => {
|
|
619
|
+
try {
|
|
620
|
+
const db = adapter.getDatabase();
|
|
621
|
+
const active = db.inTransaction;
|
|
622
|
+
return Promise.resolve({
|
|
623
|
+
success: true,
|
|
624
|
+
status: active ? "active" : "none",
|
|
625
|
+
active,
|
|
626
|
+
message: active ? "A transaction is currently active." : "No transaction is active."
|
|
627
|
+
});
|
|
628
|
+
} catch (error) {
|
|
629
|
+
return Promise.resolve(formatHandlerError(error));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
351
634
|
function createCommitTransactionTool(adapter) {
|
|
352
635
|
return {
|
|
353
636
|
name: "sqlite_transaction_commit",
|
|
354
637
|
description: "Commit the current transaction, making all changes permanent.",
|
|
355
|
-
group: "
|
|
638
|
+
group: "transactions",
|
|
356
639
|
inputSchema: z.object({}),
|
|
357
640
|
outputSchema: TransactionCommitOutputSchema,
|
|
358
641
|
annotations: write("Commit Transaction"),
|
|
@@ -374,7 +657,7 @@ function createRollbackTransactionTool(adapter) {
|
|
|
374
657
|
return {
|
|
375
658
|
name: "sqlite_transaction_rollback",
|
|
376
659
|
description: "Rollback the current transaction, discarding all changes.",
|
|
377
|
-
group: "
|
|
660
|
+
group: "transactions",
|
|
378
661
|
outputSchema: TransactionRollbackOutputSchema,
|
|
379
662
|
inputSchema: z.object({}),
|
|
380
663
|
annotations: write("Rollback Transaction"),
|
|
@@ -396,7 +679,7 @@ function createSavepointTool(adapter) {
|
|
|
396
679
|
return {
|
|
397
680
|
name: "sqlite_transaction_savepoint",
|
|
398
681
|
description: "Create a savepoint within the current transaction for partial rollback.",
|
|
399
|
-
group: "
|
|
682
|
+
group: "transactions",
|
|
400
683
|
outputSchema: TransactionSavepointOutputSchema,
|
|
401
684
|
inputSchema: SavepointSchema,
|
|
402
685
|
annotations: write("Create Savepoint"),
|
|
@@ -429,7 +712,7 @@ function createReleaseSavepointTool(adapter) {
|
|
|
429
712
|
return {
|
|
430
713
|
name: "sqlite_transaction_release",
|
|
431
714
|
description: "Release a savepoint, keeping the changes made since it was created.",
|
|
432
|
-
group: "
|
|
715
|
+
group: "transactions",
|
|
433
716
|
inputSchema: SavepointSchema,
|
|
434
717
|
outputSchema: TransactionReleaseOutputSchema,
|
|
435
718
|
annotations: write("Release Savepoint"),
|
|
@@ -462,7 +745,7 @@ function createRollbackToSavepointTool(adapter) {
|
|
|
462
745
|
return {
|
|
463
746
|
name: "sqlite_transaction_rollback_to",
|
|
464
747
|
description: "Rollback to a savepoint, discarding changes made after it was created.",
|
|
465
|
-
group: "
|
|
748
|
+
group: "transactions",
|
|
466
749
|
inputSchema: SavepointSchema,
|
|
467
750
|
outputSchema: TransactionRollbackToOutputSchema,
|
|
468
751
|
annotations: write("Rollback to Savepoint"),
|
|
@@ -495,7 +778,7 @@ function createExecuteInTransactionTool(adapter) {
|
|
|
495
778
|
return {
|
|
496
779
|
name: "sqlite_transaction_execute",
|
|
497
780
|
description: "Execute multiple SQL statements in a single transaction. Automatically commits on success or rolls back on error.",
|
|
498
|
-
group: "
|
|
781
|
+
group: "transactions",
|
|
499
782
|
outputSchema: TransactionExecuteOutputSchema,
|
|
500
783
|
inputSchema: ExecuteInTransactionSchema,
|
|
501
784
|
annotations: write("Execute in Transaction"),
|
|
@@ -504,18 +787,28 @@ function createExecuteInTransactionTool(adapter) {
|
|
|
504
787
|
let input;
|
|
505
788
|
try {
|
|
506
789
|
input = ExecuteInTransactionSchema.parse(params);
|
|
790
|
+
if (input.statements.length === 0) {
|
|
791
|
+
throw new ValidationError("Must provide at least one SQL statement");
|
|
792
|
+
}
|
|
507
793
|
} catch (error) {
|
|
794
|
+
const errObj = formatHandlerError(error);
|
|
508
795
|
return {
|
|
509
|
-
...
|
|
510
|
-
message: "",
|
|
796
|
+
...errObj,
|
|
797
|
+
message: errObj.error || "Transaction execution failed",
|
|
511
798
|
statementsExecuted: 0,
|
|
512
799
|
results: []
|
|
513
800
|
};
|
|
514
801
|
}
|
|
515
802
|
const results = [];
|
|
516
803
|
let success = true;
|
|
804
|
+
let transactionStarted = false;
|
|
805
|
+
const db = adapter.getDatabase();
|
|
806
|
+
const originallyInTransaction = db.inTransaction;
|
|
517
807
|
try {
|
|
518
|
-
|
|
808
|
+
if (!originallyInTransaction) {
|
|
809
|
+
adapter.beginTransaction();
|
|
810
|
+
transactionStarted = true;
|
|
811
|
+
}
|
|
519
812
|
for (const statement of input.statements) {
|
|
520
813
|
try {
|
|
521
814
|
const isSelect = statement.trim().toUpperCase().startsWith("SELECT");
|
|
@@ -549,25 +842,38 @@ function createExecuteInTransactionTool(adapter) {
|
|
|
549
842
|
success = false;
|
|
550
843
|
}
|
|
551
844
|
}
|
|
552
|
-
|
|
845
|
+
if (transactionStarted) {
|
|
846
|
+
adapter.commitTransaction();
|
|
847
|
+
}
|
|
553
848
|
return {
|
|
554
849
|
success,
|
|
850
|
+
error: success ? void 0 : "Transaction completed with errors",
|
|
555
851
|
message: success ? "Transaction completed successfully" : "Transaction completed with errors",
|
|
556
852
|
statementsExecuted: input.statements.length,
|
|
557
853
|
results
|
|
558
854
|
};
|
|
559
855
|
} catch (error) {
|
|
560
856
|
let rollbackFailure;
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
857
|
+
if (transactionStarted) {
|
|
858
|
+
try {
|
|
859
|
+
adapter.rollbackTransaction();
|
|
860
|
+
} catch (rbError) {
|
|
861
|
+
rollbackFailure = rbError instanceof Error ? rbError.message : String(rbError);
|
|
862
|
+
}
|
|
863
|
+
} else if (originallyInTransaction && input.rollbackOnError) {
|
|
864
|
+
try {
|
|
865
|
+
adapter.rollbackTransaction();
|
|
866
|
+
} catch (rbError) {
|
|
867
|
+
rollbackFailure = rbError instanceof Error ? rbError.message : String(rbError);
|
|
868
|
+
}
|
|
565
869
|
}
|
|
566
870
|
const message = error instanceof Error ? error.message : String(error);
|
|
567
871
|
const formatted = formatHandlerError(error);
|
|
568
872
|
let rollbackMessage = `Transaction rolled back: ${message}`;
|
|
569
873
|
if (rollbackFailure) {
|
|
570
874
|
rollbackMessage += ` (rollback error: ${rollbackFailure})`;
|
|
875
|
+
} else if (!transactionStarted && !originallyInTransaction) {
|
|
876
|
+
rollbackMessage = `Transaction failed to start: ${message}`;
|
|
571
877
|
}
|
|
572
878
|
return {
|
|
573
879
|
...formatted,
|
|
@@ -580,7 +886,13 @@ function createExecuteInTransactionTool(adapter) {
|
|
|
580
886
|
}
|
|
581
887
|
};
|
|
582
888
|
}
|
|
583
|
-
var coerceNumber = (val) =>
|
|
889
|
+
var coerceNumber = (val) => {
|
|
890
|
+
if (typeof val === "string") {
|
|
891
|
+
const num = Number(val);
|
|
892
|
+
return isNaN(num) ? void 0 : num;
|
|
893
|
+
}
|
|
894
|
+
return val;
|
|
895
|
+
};
|
|
584
896
|
var coerceEnumValues2 = (allowed) => (val) => typeof val === "string" && allowed.includes(val) ? val : void 0;
|
|
585
897
|
var VALID_DIRECTIONS = ["lag", "lead"];
|
|
586
898
|
var RowNumberSchema = z.object({
|
|
@@ -591,7 +903,7 @@ var RowNumberSchema = z.object({
|
|
|
591
903
|
whereClause: z.string().optional().describe("Optional WHERE clause"),
|
|
592
904
|
limit: z.preprocess(
|
|
593
905
|
coerceNumber,
|
|
594
|
-
z.number().optional().default(
|
|
906
|
+
z.number().optional().default(20).describe("Maximum rows to return")
|
|
595
907
|
)
|
|
596
908
|
});
|
|
597
909
|
var RankSchema = z.object({
|
|
@@ -606,7 +918,7 @@ var RankSchema = z.object({
|
|
|
606
918
|
whereClause: z.string().optional().describe("Optional WHERE clause"),
|
|
607
919
|
limit: z.preprocess(
|
|
608
920
|
coerceNumber,
|
|
609
|
-
z.number().optional().default(
|
|
921
|
+
z.number().optional().default(20).describe("Maximum rows to return")
|
|
610
922
|
)
|
|
611
923
|
});
|
|
612
924
|
var LagLeadSchema = z.object({
|
|
@@ -624,7 +936,7 @@ var LagLeadSchema = z.object({
|
|
|
624
936
|
whereClause: z.string().optional().describe("Optional WHERE clause"),
|
|
625
937
|
limit: z.preprocess(
|
|
626
938
|
coerceNumber,
|
|
627
|
-
z.number().optional().default(
|
|
939
|
+
z.number().optional().default(20).describe("Maximum rows to return")
|
|
628
940
|
)
|
|
629
941
|
});
|
|
630
942
|
var RunningTotalSchema = z.object({
|
|
@@ -636,7 +948,7 @@ var RunningTotalSchema = z.object({
|
|
|
636
948
|
whereClause: z.string().optional().describe("Optional WHERE clause"),
|
|
637
949
|
limit: z.preprocess(
|
|
638
950
|
coerceNumber,
|
|
639
|
-
z.number().optional().default(
|
|
951
|
+
z.number().optional().default(20).describe("Maximum rows to return")
|
|
640
952
|
)
|
|
641
953
|
});
|
|
642
954
|
var MovingAverageSchema = z.object({
|
|
@@ -652,7 +964,7 @@ var MovingAverageSchema = z.object({
|
|
|
652
964
|
whereClause: z.string().optional().describe("Optional WHERE clause"),
|
|
653
965
|
limit: z.preprocess(
|
|
654
966
|
coerceNumber,
|
|
655
|
-
z.number().optional().default(
|
|
967
|
+
z.number().optional().default(20).describe("Maximum rows to return")
|
|
656
968
|
)
|
|
657
969
|
});
|
|
658
970
|
var NtileSchema = z.object({
|
|
@@ -667,7 +979,7 @@ var NtileSchema = z.object({
|
|
|
667
979
|
whereClause: z.string().optional().describe("Optional WHERE clause"),
|
|
668
980
|
limit: z.preprocess(
|
|
669
981
|
coerceNumber,
|
|
670
|
-
z.number().optional().default(
|
|
982
|
+
z.number().optional().default(20).describe("Maximum rows to return")
|
|
671
983
|
)
|
|
672
984
|
});
|
|
673
985
|
async function validateTableExists2(adapter, table) {
|
|
@@ -728,11 +1040,68 @@ async function validateOrderByColumns(adapter, table, orderBy) {
|
|
|
728
1040
|
await validateColumnInTable(adapter, table, colName);
|
|
729
1041
|
}
|
|
730
1042
|
}
|
|
731
|
-
function
|
|
732
|
-
if (selectColumns
|
|
733
|
-
return
|
|
1043
|
+
async function resolveSelectColumns(adapter, table, selectColumns, rankCol) {
|
|
1044
|
+
if (selectColumns && selectColumns.length > 0) {
|
|
1045
|
+
return {
|
|
1046
|
+
columnList: selectColumns.map((c) => `"${c}"`).join(", ")
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
const tableInfo = await adapter.executeReadQuery(
|
|
1050
|
+
`PRAGMA table_info("${table}")`
|
|
1051
|
+
);
|
|
1052
|
+
const TEXT_TYPES = /* @__PURE__ */ new Set([
|
|
1053
|
+
"text",
|
|
1054
|
+
"blob",
|
|
1055
|
+
"clob",
|
|
1056
|
+
"varchar",
|
|
1057
|
+
"nvarchar",
|
|
1058
|
+
"char"
|
|
1059
|
+
]);
|
|
1060
|
+
const LONG_CONTENT_PATTERNS = [
|
|
1061
|
+
"description",
|
|
1062
|
+
"body",
|
|
1063
|
+
"bio",
|
|
1064
|
+
"content",
|
|
1065
|
+
"notes",
|
|
1066
|
+
"summary",
|
|
1067
|
+
"comment",
|
|
1068
|
+
"details",
|
|
1069
|
+
"html",
|
|
1070
|
+
"markdown",
|
|
1071
|
+
"text",
|
|
1072
|
+
"message",
|
|
1073
|
+
"payload",
|
|
1074
|
+
"raw",
|
|
1075
|
+
"data",
|
|
1076
|
+
"log",
|
|
1077
|
+
"blob"
|
|
1078
|
+
];
|
|
1079
|
+
const excluded = [];
|
|
1080
|
+
const included = [];
|
|
1081
|
+
for (const c of tableInfo.rows ?? []) {
|
|
1082
|
+
const colName = c["name"];
|
|
1083
|
+
const colType = (c["type"] ?? "").toLowerCase();
|
|
1084
|
+
const nameLower = colName.toLowerCase();
|
|
1085
|
+
const isText = [...TEXT_TYPES].some(
|
|
1086
|
+
(t) => colType === t || colType.startsWith(t)
|
|
1087
|
+
);
|
|
1088
|
+
const isRankCol = rankCol ? nameLower === rankCol.toLowerCase() : false;
|
|
1089
|
+
const isLongContent = LONG_CONTENT_PATTERNS.some(
|
|
1090
|
+
(p) => nameLower === p || nameLower.endsWith(`_${p}`) || nameLower.startsWith(`${p}_`)
|
|
1091
|
+
);
|
|
1092
|
+
if (isText && !isRankCol && isLongContent) {
|
|
1093
|
+
excluded.push(colName);
|
|
1094
|
+
} else {
|
|
1095
|
+
included.push(colName);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
if (excluded.length > 0 && included.length > 0) {
|
|
1099
|
+
return {
|
|
1100
|
+
columnList: included.map((c) => `"${c}"`).join(", "),
|
|
1101
|
+
hint: `Excluded ${excluded.length} long-content column(s) (${excluded.join(", ")}) to reduce payload. Use selectColumns to override.`
|
|
1102
|
+
};
|
|
734
1103
|
}
|
|
735
|
-
return
|
|
1104
|
+
return { columnList: "*" };
|
|
736
1105
|
}
|
|
737
1106
|
function getWindowTools(adapter) {
|
|
738
1107
|
return [
|
|
@@ -758,7 +1127,11 @@ function createRowNumberTool(adapter) {
|
|
|
758
1127
|
const input = RowNumberSchema.parse(params);
|
|
759
1128
|
await validateTableExists2(adapter, input.table);
|
|
760
1129
|
await validateOrderByColumns(adapter, input.table, input.orderBy);
|
|
761
|
-
const columns =
|
|
1130
|
+
const { columnList: columns, hint } = await resolveSelectColumns(
|
|
1131
|
+
adapter,
|
|
1132
|
+
input.table,
|
|
1133
|
+
input.selectColumns
|
|
1134
|
+
);
|
|
762
1135
|
const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
|
|
763
1136
|
let sql = `
|
|
764
1137
|
SELECT ${columns},
|
|
@@ -771,11 +1144,13 @@ function createRowNumberTool(adapter) {
|
|
|
771
1144
|
}
|
|
772
1145
|
sql += ` LIMIT ${input.limit}`;
|
|
773
1146
|
const result = await adapter.executeReadQuery(sql);
|
|
774
|
-
|
|
1147
|
+
const response = {
|
|
775
1148
|
success: true,
|
|
776
1149
|
rowCount: result.rows?.length ?? 0,
|
|
777
1150
|
rows: result.rows
|
|
778
1151
|
};
|
|
1152
|
+
if (hint) response["hint"] = hint;
|
|
1153
|
+
return response;
|
|
779
1154
|
} catch (error) {
|
|
780
1155
|
return formatHandlerError(error);
|
|
781
1156
|
}
|
|
@@ -796,7 +1171,11 @@ function createRankTool(adapter) {
|
|
|
796
1171
|
const input = RankSchema.parse(params);
|
|
797
1172
|
await validateTableExists2(adapter, input.table);
|
|
798
1173
|
await validateOrderByColumns(adapter, input.table, input.orderBy);
|
|
799
|
-
const columns =
|
|
1174
|
+
const { columnList: columns, hint } = await resolveSelectColumns(
|
|
1175
|
+
adapter,
|
|
1176
|
+
input.table,
|
|
1177
|
+
input.selectColumns
|
|
1178
|
+
);
|
|
800
1179
|
const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
|
|
801
1180
|
const rankFunc = input.rankType.toUpperCase();
|
|
802
1181
|
let sql = `
|
|
@@ -810,12 +1189,14 @@ function createRankTool(adapter) {
|
|
|
810
1189
|
}
|
|
811
1190
|
sql += ` LIMIT ${input.limit}`;
|
|
812
1191
|
const result = await adapter.executeReadQuery(sql);
|
|
813
|
-
|
|
1192
|
+
const response = {
|
|
814
1193
|
success: true,
|
|
815
1194
|
rankType: input.rankType,
|
|
816
1195
|
rowCount: result.rows?.length ?? 0,
|
|
817
1196
|
rows: result.rows
|
|
818
1197
|
};
|
|
1198
|
+
if (hint) response["hint"] = hint;
|
|
1199
|
+
return response;
|
|
819
1200
|
} catch (error) {
|
|
820
1201
|
return formatHandlerError(error);
|
|
821
1202
|
}
|
|
@@ -849,7 +1230,12 @@ function createLagLeadTool(adapter) {
|
|
|
849
1230
|
await validateTableExists2(adapter, input.table);
|
|
850
1231
|
await validateColumnInTable(adapter, input.table, input.column);
|
|
851
1232
|
await validateOrderByColumns(adapter, input.table, input.orderBy);
|
|
852
|
-
const columns =
|
|
1233
|
+
const { columnList: columns, hint } = await resolveSelectColumns(
|
|
1234
|
+
adapter,
|
|
1235
|
+
input.table,
|
|
1236
|
+
input.selectColumns,
|
|
1237
|
+
input.column
|
|
1238
|
+
);
|
|
853
1239
|
const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
|
|
854
1240
|
const func = normalizedDirection.toUpperCase();
|
|
855
1241
|
const defaultVal = input.defaultValue !== void 0 ? `, ${input.defaultValue}` : "";
|
|
@@ -864,13 +1250,15 @@ function createLagLeadTool(adapter) {
|
|
|
864
1250
|
}
|
|
865
1251
|
sql += ` LIMIT ${input.limit}`;
|
|
866
1252
|
const result = await adapter.executeReadQuery(sql);
|
|
867
|
-
|
|
1253
|
+
const response = {
|
|
868
1254
|
success: true,
|
|
869
1255
|
direction: normalizedDirection,
|
|
870
1256
|
offset: input.offset,
|
|
871
1257
|
rowCount: result.rows?.length ?? 0,
|
|
872
1258
|
rows: result.rows
|
|
873
1259
|
};
|
|
1260
|
+
if (hint) response["hint"] = hint;
|
|
1261
|
+
return response;
|
|
874
1262
|
} catch (error) {
|
|
875
1263
|
return formatHandlerError(error);
|
|
876
1264
|
}
|
|
@@ -894,7 +1282,12 @@ function createRunningTotalTool(adapter) {
|
|
|
894
1282
|
await validateTableExists2(adapter, input.table);
|
|
895
1283
|
await validateColumnInTable(adapter, input.table, input.column);
|
|
896
1284
|
await validateOrderByColumns(adapter, input.table, input.orderBy);
|
|
897
|
-
const columns =
|
|
1285
|
+
const { columnList: columns, hint } = await resolveSelectColumns(
|
|
1286
|
+
adapter,
|
|
1287
|
+
input.table,
|
|
1288
|
+
input.selectColumns,
|
|
1289
|
+
input.column
|
|
1290
|
+
);
|
|
898
1291
|
const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
|
|
899
1292
|
let sql = `
|
|
900
1293
|
SELECT ${columns},
|
|
@@ -907,12 +1300,14 @@ function createRunningTotalTool(adapter) {
|
|
|
907
1300
|
}
|
|
908
1301
|
sql += ` LIMIT ${input.limit}`;
|
|
909
1302
|
const result = await adapter.executeReadQuery(sql);
|
|
910
|
-
|
|
1303
|
+
const response = {
|
|
911
1304
|
success: true,
|
|
912
1305
|
valueColumn: input.column,
|
|
913
1306
|
rowCount: result.rows?.length ?? 0,
|
|
914
1307
|
rows: result.rows
|
|
915
1308
|
};
|
|
1309
|
+
if (hint) response["hint"] = hint;
|
|
1310
|
+
return response;
|
|
916
1311
|
} catch (error) {
|
|
917
1312
|
return formatHandlerError(error);
|
|
918
1313
|
}
|
|
@@ -945,7 +1340,12 @@ function createMovingAverageTool(adapter) {
|
|
|
945
1340
|
await validateTableExists2(adapter, input.table);
|
|
946
1341
|
await validateColumnInTable(adapter, input.table, input.column);
|
|
947
1342
|
await validateOrderByColumns(adapter, input.table, input.orderBy);
|
|
948
|
-
const columns =
|
|
1343
|
+
const { columnList: columns, hint } = await resolveSelectColumns(
|
|
1344
|
+
adapter,
|
|
1345
|
+
input.table,
|
|
1346
|
+
input.selectColumns,
|
|
1347
|
+
input.column
|
|
1348
|
+
);
|
|
949
1349
|
const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
|
|
950
1350
|
const preceding = input.windowSize - 1;
|
|
951
1351
|
let sql = `
|
|
@@ -959,13 +1359,15 @@ function createMovingAverageTool(adapter) {
|
|
|
959
1359
|
}
|
|
960
1360
|
sql += ` LIMIT ${input.limit}`;
|
|
961
1361
|
const result = await adapter.executeReadQuery(sql);
|
|
962
|
-
|
|
1362
|
+
const response = {
|
|
963
1363
|
success: true,
|
|
964
1364
|
valueColumn: input.column,
|
|
965
1365
|
windowSize: input.windowSize,
|
|
966
1366
|
rowCount: result.rows?.length ?? 0,
|
|
967
1367
|
rows: result.rows
|
|
968
1368
|
};
|
|
1369
|
+
if (hint) response["hint"] = hint;
|
|
1370
|
+
return response;
|
|
969
1371
|
} catch (error) {
|
|
970
1372
|
return formatHandlerError(error);
|
|
971
1373
|
}
|
|
@@ -995,7 +1397,11 @@ function createNtileTool(adapter) {
|
|
|
995
1397
|
}
|
|
996
1398
|
await validateTableExists2(adapter, input.table);
|
|
997
1399
|
await validateOrderByColumns(adapter, input.table, input.orderBy);
|
|
998
|
-
const columns =
|
|
1400
|
+
const { columnList: columns, hint } = await resolveSelectColumns(
|
|
1401
|
+
adapter,
|
|
1402
|
+
input.table,
|
|
1403
|
+
input.selectColumns
|
|
1404
|
+
);
|
|
999
1405
|
const partition = input.partitionBy ? `PARTITION BY ${input.partitionBy}` : "";
|
|
1000
1406
|
let sql = `
|
|
1001
1407
|
SELECT ${columns},
|
|
@@ -1008,12 +1414,14 @@ function createNtileTool(adapter) {
|
|
|
1008
1414
|
}
|
|
1009
1415
|
sql += ` LIMIT ${input.limit}`;
|
|
1010
1416
|
const result = await adapter.executeReadQuery(sql);
|
|
1011
|
-
|
|
1417
|
+
const response = {
|
|
1012
1418
|
success: true,
|
|
1013
1419
|
buckets: input.buckets,
|
|
1014
1420
|
rowCount: result.rows?.length ?? 0,
|
|
1015
1421
|
rows: result.rows
|
|
1016
1422
|
};
|
|
1423
|
+
if (hint) response["hint"] = hint;
|
|
1424
|
+
return response;
|
|
1017
1425
|
} catch (error) {
|
|
1018
1426
|
return formatHandlerError(error);
|
|
1019
1427
|
}
|
|
@@ -1211,11 +1619,9 @@ function ensureSpatialite(adapter) {
|
|
|
1211
1619
|
if (!isSpatialiteLoaded(adapter)) {
|
|
1212
1620
|
const result = tryLoadSpatialite(adapter);
|
|
1213
1621
|
if (!result.success) {
|
|
1214
|
-
throw new
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
"connection" /* CONNECTION */
|
|
1218
|
-
);
|
|
1622
|
+
throw new ExtensionNotAvailableError("SpatiaLite", {
|
|
1623
|
+
suggestion: "Install mod_spatialite and set SPATIALITE_PATH environment variable."
|
|
1624
|
+
});
|
|
1219
1625
|
}
|
|
1220
1626
|
}
|
|
1221
1627
|
}
|
|
@@ -1422,9 +1828,10 @@ function createSpatialIndexTool(adapter) {
|
|
|
1422
1828
|
}
|
|
1423
1829
|
const indexExists = async () => {
|
|
1424
1830
|
const idxCheck = await adapter.executeReadQuery(
|
|
1425
|
-
`SELECT
|
|
1831
|
+
`SELECT spatial_index_enabled FROM geometry_columns WHERE f_table_name = '${input.tableName}' AND f_geometry_column = '${input.geometryColumn}'`
|
|
1426
1832
|
);
|
|
1427
|
-
|
|
1833
|
+
const val = idxCheck.rows?.[0]?.["spatial_index_enabled"];
|
|
1834
|
+
return val === 1 || val === "1";
|
|
1428
1835
|
};
|
|
1429
1836
|
switch (input.action) {
|
|
1430
1837
|
case "create": {
|
|
@@ -1436,9 +1843,18 @@ function createSpatialIndexTool(adapter) {
|
|
|
1436
1843
|
action: "create"
|
|
1437
1844
|
};
|
|
1438
1845
|
}
|
|
1439
|
-
await adapter.executeReadQuery(
|
|
1440
|
-
`SELECT CreateSpatialIndex('${input.tableName}', '${input.geometryColumn}')`
|
|
1846
|
+
const createRes = await adapter.executeReadQuery(
|
|
1847
|
+
`SELECT CreateSpatialIndex('${input.tableName}', '${input.geometryColumn}') as res`
|
|
1441
1848
|
);
|
|
1849
|
+
if (createRes.rows?.[0]?.["res"] === 0) {
|
|
1850
|
+
return {
|
|
1851
|
+
success: false,
|
|
1852
|
+
error: `Failed to create spatial index on ${input.tableName}.${input.geometryColumn}`,
|
|
1853
|
+
code: "SPATIAL_INDEX_CREATE_FAILED",
|
|
1854
|
+
category: "internal",
|
|
1855
|
+
recoverable: false
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1442
1858
|
return {
|
|
1443
1859
|
success: true,
|
|
1444
1860
|
message: `Spatial index created on ${input.tableName}.${input.geometryColumn}`,
|
|
@@ -1454,9 +1870,18 @@ function createSpatialIndexTool(adapter) {
|
|
|
1454
1870
|
action: "drop"
|
|
1455
1871
|
};
|
|
1456
1872
|
}
|
|
1457
|
-
await adapter.executeReadQuery(
|
|
1458
|
-
`SELECT DisableSpatialIndex('${input.tableName}', '${input.geometryColumn}')`
|
|
1873
|
+
const dropRes = await adapter.executeReadQuery(
|
|
1874
|
+
`SELECT DisableSpatialIndex('${input.tableName}', '${input.geometryColumn}') as res`
|
|
1459
1875
|
);
|
|
1876
|
+
if (dropRes.rows?.[0]?.["res"] === 0) {
|
|
1877
|
+
return {
|
|
1878
|
+
success: false,
|
|
1879
|
+
error: `Failed to drop spatial index on ${input.tableName}.${input.geometryColumn}`,
|
|
1880
|
+
code: "SPATIAL_INDEX_DROP_FAILED",
|
|
1881
|
+
category: "internal",
|
|
1882
|
+
recoverable: false
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1460
1885
|
return {
|
|
1461
1886
|
success: true,
|
|
1462
1887
|
message: `Spatial index dropped from ${input.tableName}.${input.geometryColumn}`,
|
|
@@ -1786,6 +2211,19 @@ function createSpatialImportTool(adapter) {
|
|
|
1786
2211
|
if (input.format === "geojson") {
|
|
1787
2212
|
try {
|
|
1788
2213
|
JSON.parse(input.data);
|
|
2214
|
+
const geojsonCheck = await adapter.executeReadQuery(
|
|
2215
|
+
`SELECT GeomFromGeoJSON('${input.data}') as geom`
|
|
2216
|
+
);
|
|
2217
|
+
const parsedGeom2 = geojsonCheck.rows?.[0]?.["geom"];
|
|
2218
|
+
if (parsedGeom2 === null || parsedGeom2 === void 0) {
|
|
2219
|
+
return {
|
|
2220
|
+
success: false,
|
|
2221
|
+
error: `Invalid GeoJSON geometry: could not be parsed by SpatiaLite (must be a valid Geometry object)`,
|
|
2222
|
+
code: "VALIDATION_ERROR",
|
|
2223
|
+
category: "validation",
|
|
2224
|
+
recoverable: false
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
1789
2227
|
const columns2 = ["geom"];
|
|
1790
2228
|
const values2 = [
|
|
1791
2229
|
`SetSRID(GeomFromGeoJSON('${input.data}'), ${input.srid})`
|
|
@@ -2043,18 +2481,7 @@ function nativeExecuteRead(db, sql, params, log2) {
|
|
|
2043
2481
|
executionTimeMs: Date.now() - start
|
|
2044
2482
|
});
|
|
2045
2483
|
} catch (error) {
|
|
2046
|
-
|
|
2047
|
-
log2.error(`Query failed: ${message}`, {
|
|
2048
|
-
code: ERROR_CODES.DB.QUERY_FAILED.full
|
|
2049
|
-
});
|
|
2050
|
-
throw new QueryError(
|
|
2051
|
-
`Query execution failed: ${message}`,
|
|
2052
|
-
"DB_QUERY_FAILED",
|
|
2053
|
-
{
|
|
2054
|
-
sql,
|
|
2055
|
-
cause: error instanceof Error ? error : void 0
|
|
2056
|
-
}
|
|
2057
|
-
);
|
|
2484
|
+
translateSqliteError(error, sql, "Query execution", log2);
|
|
2058
2485
|
}
|
|
2059
2486
|
}
|
|
2060
2487
|
function nativeExecuteWrite(db, sql, params, log2) {
|
|
@@ -2062,21 +2489,27 @@ function nativeExecuteWrite(db, sql, params, log2) {
|
|
|
2062
2489
|
try {
|
|
2063
2490
|
const stmt = db.prepare(sql);
|
|
2064
2491
|
const normalizedParams = normalizeSqliteParams(params);
|
|
2492
|
+
if (stmt.reader) {
|
|
2493
|
+
const rows = normalizedParams ? stmt.all(...normalizedParams) : stmt.all();
|
|
2494
|
+
return Promise.resolve({
|
|
2495
|
+
rows,
|
|
2496
|
+
rowsAffected: rows.length,
|
|
2497
|
+
// .all() does not return changes, but we know it's rows.length for RETURNING
|
|
2498
|
+
executionTimeMs: Date.now() - start
|
|
2499
|
+
});
|
|
2500
|
+
}
|
|
2065
2501
|
const info = normalizedParams ? stmt.run(...normalizedParams) : stmt.run();
|
|
2066
|
-
|
|
2502
|
+
const result = {
|
|
2067
2503
|
rows: [],
|
|
2068
2504
|
rowsAffected: info.changes,
|
|
2069
2505
|
executionTimeMs: Date.now() - start
|
|
2070
|
-
}
|
|
2506
|
+
};
|
|
2507
|
+
if (info.lastInsertRowid !== void 0) {
|
|
2508
|
+
result.lastInsertId = Number(info.lastInsertRowid);
|
|
2509
|
+
}
|
|
2510
|
+
return Promise.resolve(result);
|
|
2071
2511
|
} catch (error) {
|
|
2072
|
-
|
|
2073
|
-
log2.error(`Write query failed: ${message}`, {
|
|
2074
|
-
code: ERROR_CODES.DB.QUERY_FAILED.full
|
|
2075
|
-
});
|
|
2076
|
-
throw new QueryError(`Write query failed: ${message}`, "DB_WRITE_FAILED", {
|
|
2077
|
-
sql,
|
|
2078
|
-
cause: error instanceof Error ? error : void 0
|
|
2079
|
-
});
|
|
2512
|
+
translateSqliteError(error, sql, "Write query", log2);
|
|
2080
2513
|
}
|
|
2081
2514
|
}
|
|
2082
2515
|
|
|
@@ -2085,7 +2518,7 @@ var log = logger.child("NATIVE_SQLITE");
|
|
|
2085
2518
|
var NativeSqliteAdapter = class extends DatabaseAdapter {
|
|
2086
2519
|
type = "sqlite";
|
|
2087
2520
|
name = "Native SQLite Adapter (better-sqlite3)";
|
|
2088
|
-
version =
|
|
2521
|
+
version = VERSION;
|
|
2089
2522
|
/**
|
|
2090
2523
|
* Check if this adapter uses native (better-sqlite3) backend.
|
|
2091
2524
|
* Returns true for native adapter.
|
|
@@ -2099,7 +2532,8 @@ var NativeSqliteAdapter = class extends DatabaseAdapter {
|
|
|
2099
2532
|
*/
|
|
2100
2533
|
getConfiguredPath() {
|
|
2101
2534
|
const config = this.config;
|
|
2102
|
-
|
|
2535
|
+
const path2 = config?.filePath ?? config?.connectionString ?? ":memory:";
|
|
2536
|
+
return path2;
|
|
2103
2537
|
}
|
|
2104
2538
|
db = null;
|
|
2105
2539
|
schemaManager = null;
|
|
@@ -2137,6 +2571,17 @@ var NativeSqliteAdapter = class extends DatabaseAdapter {
|
|
|
2137
2571
|
const versionResult = db.prepare("SELECT sqlite_version()").get();
|
|
2138
2572
|
return versionResult?.["sqlite_version()"] ?? "0.0.0";
|
|
2139
2573
|
}, log);
|
|
2574
|
+
if (sqliteConfig.initializationSql && Array.isArray(sqliteConfig.initializationSql)) {
|
|
2575
|
+
for (const sql of sqliteConfig.initializationSql) {
|
|
2576
|
+
db.exec(sql);
|
|
2577
|
+
}
|
|
2578
|
+
log.info(
|
|
2579
|
+
`Executed ${sqliteConfig.initializationSql.length} initialization SQL statements`,
|
|
2580
|
+
{
|
|
2581
|
+
code: "SQLITE_INIT_SQL"
|
|
2582
|
+
}
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2140
2585
|
this.schemaManager = new SchemaManager(this);
|
|
2141
2586
|
this.connected = true;
|
|
2142
2587
|
} catch (error) {
|
|
@@ -2328,6 +2773,23 @@ var NativeSqliteAdapter = class extends DatabaseAdapter {
|
|
|
2328
2773
|
listSchemas() {
|
|
2329
2774
|
return Promise.resolve(["main"]);
|
|
2330
2775
|
}
|
|
2776
|
+
/**
|
|
2777
|
+
* Get indexes, optionally for a specific table (cached via SchemaManager)
|
|
2778
|
+
*/
|
|
2779
|
+
async getIndexes(table) {
|
|
2780
|
+
this.ensureConnected();
|
|
2781
|
+
if (this.schemaManager) {
|
|
2782
|
+
if (table) {
|
|
2783
|
+
return this.schemaManager.getTableIndexes(table);
|
|
2784
|
+
}
|
|
2785
|
+
return this.schemaManager.getAllIndexes();
|
|
2786
|
+
}
|
|
2787
|
+
const all = await this.getAllIndexes();
|
|
2788
|
+
if (table) {
|
|
2789
|
+
return all.filter((i) => i.tableName === table);
|
|
2790
|
+
}
|
|
2791
|
+
return all;
|
|
2792
|
+
}
|
|
2331
2793
|
/**
|
|
2332
2794
|
* Get all indexes in the database (cached via SchemaManager)
|
|
2333
2795
|
* Required by sqlite_indexes resource
|