db-mcp 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +860 -0
  3. package/dist/adapters/DatabaseAdapter.d.ts +141 -0
  4. package/dist/adapters/DatabaseAdapter.d.ts.map +1 -0
  5. package/dist/adapters/DatabaseAdapter.js +131 -0
  6. package/dist/adapters/DatabaseAdapter.js.map +1 -0
  7. package/dist/adapters/sqlite/SchemaManager.d.ts +58 -0
  8. package/dist/adapters/sqlite/SchemaManager.d.ts.map +1 -0
  9. package/dist/adapters/sqlite/SchemaManager.js +187 -0
  10. package/dist/adapters/sqlite/SchemaManager.js.map +1 -0
  11. package/dist/adapters/sqlite/SqliteAdapter.d.ts +161 -0
  12. package/dist/adapters/sqlite/SqliteAdapter.d.ts.map +1 -0
  13. package/dist/adapters/sqlite/SqliteAdapter.js +741 -0
  14. package/dist/adapters/sqlite/SqliteAdapter.js.map +1 -0
  15. package/dist/adapters/sqlite/index.d.ts +9 -0
  16. package/dist/adapters/sqlite/index.d.ts.map +1 -0
  17. package/dist/adapters/sqlite/index.js +8 -0
  18. package/dist/adapters/sqlite/index.js.map +1 -0
  19. package/dist/adapters/sqlite/json-utils.d.ts +100 -0
  20. package/dist/adapters/sqlite/json-utils.d.ts.map +1 -0
  21. package/dist/adapters/sqlite/json-utils.js +274 -0
  22. package/dist/adapters/sqlite/json-utils.js.map +1 -0
  23. package/dist/adapters/sqlite/output-schemas.d.ts +1187 -0
  24. package/dist/adapters/sqlite/output-schemas.d.ts.map +1 -0
  25. package/dist/adapters/sqlite/output-schemas.js +1337 -0
  26. package/dist/adapters/sqlite/output-schemas.js.map +1 -0
  27. package/dist/adapters/sqlite/prompts.d.ts +13 -0
  28. package/dist/adapters/sqlite/prompts.d.ts.map +1 -0
  29. package/dist/adapters/sqlite/prompts.js +605 -0
  30. package/dist/adapters/sqlite/prompts.js.map +1 -0
  31. package/dist/adapters/sqlite/resources.d.ts +13 -0
  32. package/dist/adapters/sqlite/resources.d.ts.map +1 -0
  33. package/dist/adapters/sqlite/resources.js +251 -0
  34. package/dist/adapters/sqlite/resources.js.map +1 -0
  35. package/dist/adapters/sqlite/tools/admin.d.ts +14 -0
  36. package/dist/adapters/sqlite/tools/admin.d.ts.map +1 -0
  37. package/dist/adapters/sqlite/tools/admin.js +788 -0
  38. package/dist/adapters/sqlite/tools/admin.js.map +1 -0
  39. package/dist/adapters/sqlite/tools/core.d.ts +25 -0
  40. package/dist/adapters/sqlite/tools/core.d.ts.map +1 -0
  41. package/dist/adapters/sqlite/tools/core.js +359 -0
  42. package/dist/adapters/sqlite/tools/core.js.map +1 -0
  43. package/dist/adapters/sqlite/tools/fts.d.ts +13 -0
  44. package/dist/adapters/sqlite/tools/fts.d.ts.map +1 -0
  45. package/dist/adapters/sqlite/tools/fts.js +347 -0
  46. package/dist/adapters/sqlite/tools/fts.js.map +1 -0
  47. package/dist/adapters/sqlite/tools/geo.d.ts +14 -0
  48. package/dist/adapters/sqlite/tools/geo.d.ts.map +1 -0
  49. package/dist/adapters/sqlite/tools/geo.js +252 -0
  50. package/dist/adapters/sqlite/tools/geo.js.map +1 -0
  51. package/dist/adapters/sqlite/tools/index.d.ts +30 -0
  52. package/dist/adapters/sqlite/tools/index.d.ts.map +1 -0
  53. package/dist/adapters/sqlite/tools/index.js +61 -0
  54. package/dist/adapters/sqlite/tools/index.js.map +1 -0
  55. package/dist/adapters/sqlite/tools/json-helpers.d.ts +14 -0
  56. package/dist/adapters/sqlite/tools/json-helpers.d.ts.map +1 -0
  57. package/dist/adapters/sqlite/tools/json-helpers.js +477 -0
  58. package/dist/adapters/sqlite/tools/json-helpers.js.map +1 -0
  59. package/dist/adapters/sqlite/tools/json-operations.d.ts +14 -0
  60. package/dist/adapters/sqlite/tools/json-operations.d.ts.map +1 -0
  61. package/dist/adapters/sqlite/tools/json-operations.js +839 -0
  62. package/dist/adapters/sqlite/tools/json-operations.js.map +1 -0
  63. package/dist/adapters/sqlite/tools/stats.d.ts +15 -0
  64. package/dist/adapters/sqlite/tools/stats.d.ts.map +1 -0
  65. package/dist/adapters/sqlite/tools/stats.js +1219 -0
  66. package/dist/adapters/sqlite/tools/stats.js.map +1 -0
  67. package/dist/adapters/sqlite/tools/text.d.ts +14 -0
  68. package/dist/adapters/sqlite/tools/text.d.ts.map +1 -0
  69. package/dist/adapters/sqlite/tools/text.js +1141 -0
  70. package/dist/adapters/sqlite/tools/text.js.map +1 -0
  71. package/dist/adapters/sqlite/tools/vector.d.ts +14 -0
  72. package/dist/adapters/sqlite/tools/vector.d.ts.map +1 -0
  73. package/dist/adapters/sqlite/tools/vector.js +613 -0
  74. package/dist/adapters/sqlite/tools/vector.js.map +1 -0
  75. package/dist/adapters/sqlite/tools/virtual.d.ts +13 -0
  76. package/dist/adapters/sqlite/tools/virtual.d.ts.map +1 -0
  77. package/dist/adapters/sqlite/tools/virtual.js +930 -0
  78. package/dist/adapters/sqlite/tools/virtual.js.map +1 -0
  79. package/dist/adapters/sqlite/types.d.ts +207 -0
  80. package/dist/adapters/sqlite/types.d.ts.map +1 -0
  81. package/dist/adapters/sqlite/types.js +186 -0
  82. package/dist/adapters/sqlite/types.js.map +1 -0
  83. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts +163 -0
  84. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts.map +1 -0
  85. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js +748 -0
  86. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js.map +1 -0
  87. package/dist/adapters/sqlite-native/index.d.ts +11 -0
  88. package/dist/adapters/sqlite-native/index.d.ts.map +1 -0
  89. package/dist/adapters/sqlite-native/index.js +11 -0
  90. package/dist/adapters/sqlite-native/index.js.map +1 -0
  91. package/dist/adapters/sqlite-native/tools/spatialite.d.ts +19 -0
  92. package/dist/adapters/sqlite-native/tools/spatialite.d.ts.map +1 -0
  93. package/dist/adapters/sqlite-native/tools/spatialite.js +628 -0
  94. package/dist/adapters/sqlite-native/tools/spatialite.js.map +1 -0
  95. package/dist/adapters/sqlite-native/tools/transactions.d.ts +12 -0
  96. package/dist/adapters/sqlite-native/tools/transactions.d.ts.map +1 -0
  97. package/dist/adapters/sqlite-native/tools/transactions.js +255 -0
  98. package/dist/adapters/sqlite-native/tools/transactions.js.map +1 -0
  99. package/dist/adapters/sqlite-native/tools/window.d.ts +12 -0
  100. package/dist/adapters/sqlite-native/tools/window.d.ts.map +1 -0
  101. package/dist/adapters/sqlite-native/tools/window.js +370 -0
  102. package/dist/adapters/sqlite-native/tools/window.js.map +1 -0
  103. package/dist/auth/AuthorizationServerDiscovery.d.ts +90 -0
  104. package/dist/auth/AuthorizationServerDiscovery.d.ts.map +1 -0
  105. package/dist/auth/AuthorizationServerDiscovery.js +204 -0
  106. package/dist/auth/AuthorizationServerDiscovery.js.map +1 -0
  107. package/dist/auth/OAuthResourceServer.d.ts +65 -0
  108. package/dist/auth/OAuthResourceServer.d.ts.map +1 -0
  109. package/dist/auth/OAuthResourceServer.js +121 -0
  110. package/dist/auth/OAuthResourceServer.js.map +1 -0
  111. package/dist/auth/TokenValidator.d.ts +60 -0
  112. package/dist/auth/TokenValidator.d.ts.map +1 -0
  113. package/dist/auth/TokenValidator.js +235 -0
  114. package/dist/auth/TokenValidator.js.map +1 -0
  115. package/dist/auth/errors.d.ts +74 -0
  116. package/dist/auth/errors.d.ts.map +1 -0
  117. package/dist/auth/errors.js +133 -0
  118. package/dist/auth/errors.js.map +1 -0
  119. package/dist/auth/index.d.ts +13 -0
  120. package/dist/auth/index.d.ts.map +1 -0
  121. package/dist/auth/index.js +15 -0
  122. package/dist/auth/index.js.map +1 -0
  123. package/dist/auth/middleware.d.ts +81 -0
  124. package/dist/auth/middleware.d.ts.map +1 -0
  125. package/dist/auth/middleware.js +291 -0
  126. package/dist/auth/middleware.js.map +1 -0
  127. package/dist/auth/scopes.d.ts +136 -0
  128. package/dist/auth/scopes.d.ts.map +1 -0
  129. package/dist/auth/scopes.js +349 -0
  130. package/dist/auth/scopes.js.map +1 -0
  131. package/dist/auth/types.d.ts +257 -0
  132. package/dist/auth/types.d.ts.map +1 -0
  133. package/dist/auth/types.js +8 -0
  134. package/dist/auth/types.js.map +1 -0
  135. package/dist/cli.d.ts +8 -0
  136. package/dist/cli.d.ts.map +1 -0
  137. package/dist/cli.js +236 -0
  138. package/dist/cli.js.map +1 -0
  139. package/dist/constants/ServerInstructions.d.ts +45 -0
  140. package/dist/constants/ServerInstructions.d.ts.map +1 -0
  141. package/dist/constants/ServerInstructions.js +356 -0
  142. package/dist/constants/ServerInstructions.js.map +1 -0
  143. package/dist/filtering/ToolConstants.d.ts +34 -0
  144. package/dist/filtering/ToolConstants.d.ts.map +1 -0
  145. package/dist/filtering/ToolConstants.js +174 -0
  146. package/dist/filtering/ToolConstants.js.map +1 -0
  147. package/dist/filtering/ToolFilter.d.ts +82 -0
  148. package/dist/filtering/ToolFilter.d.ts.map +1 -0
  149. package/dist/filtering/ToolFilter.js +296 -0
  150. package/dist/filtering/ToolFilter.js.map +1 -0
  151. package/dist/index.d.ts +13 -0
  152. package/dist/index.d.ts.map +1 -0
  153. package/dist/index.js +17 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/server/McpServer.d.ts +61 -0
  156. package/dist/server/McpServer.d.ts.map +1 -0
  157. package/dist/server/McpServer.js +270 -0
  158. package/dist/server/McpServer.js.map +1 -0
  159. package/dist/transports/http.d.ts +134 -0
  160. package/dist/transports/http.d.ts.map +1 -0
  161. package/dist/transports/http.js +516 -0
  162. package/dist/transports/http.js.map +1 -0
  163. package/dist/transports/index.d.ts +5 -0
  164. package/dist/transports/index.d.ts.map +1 -0
  165. package/dist/transports/index.js +5 -0
  166. package/dist/transports/index.js.map +1 -0
  167. package/dist/types/index.d.ts +380 -0
  168. package/dist/types/index.d.ts.map +1 -0
  169. package/dist/types/index.js +68 -0
  170. package/dist/types/index.js.map +1 -0
  171. package/dist/utils/annotations.d.ts +44 -0
  172. package/dist/utils/annotations.d.ts.map +1 -0
  173. package/dist/utils/annotations.js +77 -0
  174. package/dist/utils/annotations.js.map +1 -0
  175. package/dist/utils/errors.d.ts +155 -0
  176. package/dist/utils/errors.d.ts.map +1 -0
  177. package/dist/utils/errors.js +329 -0
  178. package/dist/utils/errors.js.map +1 -0
  179. package/dist/utils/identifiers.d.ts +121 -0
  180. package/dist/utils/identifiers.d.ts.map +1 -0
  181. package/dist/utils/identifiers.js +319 -0
  182. package/dist/utils/identifiers.js.map +1 -0
  183. package/dist/utils/index.d.ts +7 -0
  184. package/dist/utils/index.d.ts.map +1 -0
  185. package/dist/utils/index.js +7 -0
  186. package/dist/utils/index.js.map +1 -0
  187. package/dist/utils/insightsManager.d.ts +39 -0
  188. package/dist/utils/insightsManager.d.ts.map +1 -0
  189. package/dist/utils/insightsManager.js +63 -0
  190. package/dist/utils/insightsManager.js.map +1 -0
  191. package/dist/utils/logger.d.ts +189 -0
  192. package/dist/utils/logger.d.ts.map +1 -0
  193. package/dist/utils/logger.js +394 -0
  194. package/dist/utils/logger.js.map +1 -0
  195. package/dist/utils/progress-utils.d.ts +54 -0
  196. package/dist/utils/progress-utils.d.ts.map +1 -0
  197. package/dist/utils/progress-utils.js +74 -0
  198. package/dist/utils/progress-utils.js.map +1 -0
  199. package/dist/utils/resourceAnnotations.d.ts +36 -0
  200. package/dist/utils/resourceAnnotations.d.ts.map +1 -0
  201. package/dist/utils/resourceAnnotations.js +57 -0
  202. package/dist/utils/resourceAnnotations.js.map +1 -0
  203. package/dist/utils/where-clause.d.ts +41 -0
  204. package/dist/utils/where-clause.d.ts.map +1 -0
  205. package/dist/utils/where-clause.js +116 -0
  206. package/dist/utils/where-clause.js.map +1 -0
  207. package/package.json +83 -0
  208. package/server.json +53 -0
@@ -0,0 +1,930 @@
1
+ /**
2
+ * SQLite Virtual Table Tools
3
+ *
4
+ * Create and manage virtual tables for CSV, R-Tree, generation, etc.
5
+ * 13 tools total.
6
+ */
7
+ import * as path from "node:path";
8
+ import { z } from "zod";
9
+ import { readOnly, idempotent, destructive, admin, } from "../../../utils/annotations.js";
10
+ import { buildProgressContext, sendProgress, } from "../../../utils/progress-utils.js";
11
+ import { sanitizeIdentifier } from "../../../utils/index.js";
12
+ import { GenerateSeriesOutputSchema, CreateTableOutputSchema, ListViewsOutputSchema, DropTableOutputSchema, VacuumOutputSchema, } from "../output-schemas.js";
13
+ import { isSpatialiteSystemView, isSpatialiteSystemTable, isSpatialiteSystemIndex, } from "./core.js";
14
+ // Virtual table schemas
15
+ const GenerateSeriesSchema = z.object({
16
+ start: z.number().describe("Start value"),
17
+ stop: z.number().describe("Stop value"),
18
+ step: z.number().optional().default(1).describe("Step value"),
19
+ });
20
+ const CreateViewSchema = z.object({
21
+ viewName: z.string().describe("Name of the view"),
22
+ selectQuery: z.string().describe("SELECT query for view definition"),
23
+ replace: z.boolean().optional().default(false),
24
+ });
25
+ const ListViewsSchema = z.object({
26
+ pattern: z
27
+ .string()
28
+ .optional()
29
+ .describe("Optional LIKE pattern to filter views"),
30
+ excludeSystemViews: z
31
+ .boolean()
32
+ .optional()
33
+ .default(true)
34
+ .describe("Exclude SpatiaLite system views (default: true). Set to false to include all views."),
35
+ });
36
+ const DropViewSchema = z.object({
37
+ viewName: z.string().describe("Name of the view to drop"),
38
+ ifExists: z.boolean().optional().default(true),
39
+ });
40
+ const DbStatSchema = z.object({
41
+ table: z.string().optional().describe("Optional table name to filter"),
42
+ summarize: z
43
+ .boolean()
44
+ .optional()
45
+ .default(false)
46
+ .describe("If true, return aggregated per-table stats instead of raw page-level data"),
47
+ limit: z
48
+ .number()
49
+ .optional()
50
+ .default(100)
51
+ .describe("Maximum number of tables/pages to return (default: 100)"),
52
+ excludeSystemTables: z
53
+ .boolean()
54
+ .optional()
55
+ .default(false)
56
+ .describe("Exclude SpatiaLite system tables and indexes from results (default: false)"),
57
+ });
58
+ const VacuumSchema = z.object({
59
+ into: z.string().optional().describe("Optional file path to vacuum into"),
60
+ });
61
+ // New virtual table schemas
62
+ const ListVirtualTablesSchema = z.object({
63
+ pattern: z.string().optional().describe("Optional LIKE pattern to filter"),
64
+ });
65
+ const VirtualTableInfoSchema = z.object({
66
+ tableName: z.string().describe("Name of the virtual table"),
67
+ });
68
+ const DropVirtualTableSchema = z.object({
69
+ tableName: z.string().describe("Name of the virtual table to drop"),
70
+ ifExists: z.boolean().optional().default(true),
71
+ });
72
+ const CreateCsvTableSchema = z.object({
73
+ tableName: z.string().describe("Name for the virtual table"),
74
+ filePath: z.string().describe("Path to the CSV file"),
75
+ header: z.boolean().optional().default(true).describe("First row is header"),
76
+ delimiter: z.string().optional().default(",").describe("Column delimiter"),
77
+ columns: z
78
+ .array(z.string())
79
+ .optional()
80
+ .describe("Manual column names if no header"),
81
+ });
82
+ const AnalyzeCsvSchemaSchema = z.object({
83
+ filePath: z.string().describe("Path to the CSV file"),
84
+ sampleRows: z.number().optional().default(100).describe("Rows to sample"),
85
+ delimiter: z.string().optional().default(",").describe("Column delimiter"),
86
+ });
87
+ const CreateRtreeTableSchema = z.object({
88
+ tableName: z.string().describe("Name for the R-Tree table"),
89
+ dimensions: z
90
+ .number()
91
+ .min(2)
92
+ .max(5)
93
+ .optional()
94
+ .default(2)
95
+ .describe("Number of dimensions (2-5)"),
96
+ idColumn: z.string().optional().default("id").describe("ID column name"),
97
+ });
98
+ const CreateSeriesTableSchema = z.object({
99
+ tableName: z.string().describe("Name for the series table"),
100
+ start: z.number().describe("Start value"),
101
+ stop: z.number().describe("Stop value"),
102
+ step: z.number().optional().default(1).describe("Step value"),
103
+ columnName: z.string().optional().default("value").describe("Column name"),
104
+ });
105
+ /**
106
+ * Get all virtual table tools
107
+ */
108
+ export function getVirtualTools(adapter) {
109
+ return [
110
+ createGenerateSeriesTool(adapter),
111
+ createCreateViewTool(adapter),
112
+ createListViewsTool(adapter),
113
+ createDropViewTool(adapter),
114
+ createDbStatTool(adapter),
115
+ createVacuumTool(adapter),
116
+ // New virtual table tools
117
+ createListVirtualTablesTool(adapter),
118
+ createVirtualTableInfoTool(adapter),
119
+ createDropVirtualTableTool(adapter),
120
+ createCsvTableTool(adapter),
121
+ createAnalyzeCsvSchemaTool(adapter),
122
+ createRtreeTableTool(adapter),
123
+ createSeriesTableTool(adapter),
124
+ ];
125
+ }
126
+ /**
127
+ * Generate series of numbers
128
+ * Note: Uses pure JS implementation since better-sqlite3's bundled SQLite
129
+ * is not compiled with SQLITE_ENABLE_SERIES (required for native generate_series).
130
+ */
131
+ function createGenerateSeriesTool(_adapter) {
132
+ return {
133
+ name: "sqlite_generate_series",
134
+ description: "Generate a series of numbers using generate_series() virtual table.",
135
+ group: "admin",
136
+ inputSchema: GenerateSeriesSchema,
137
+ outputSchema: GenerateSeriesOutputSchema,
138
+ requiredScopes: ["read"],
139
+ annotations: readOnly("Generate Series"),
140
+ handler: (params, _context) => {
141
+ const input = GenerateSeriesSchema.parse(params);
142
+ // Generate in JS - better-sqlite3 doesn't include SQLITE_ENABLE_SERIES
143
+ const values = [];
144
+ for (let i = input.start; input.step > 0 ? i <= input.stop : i >= input.stop; i += input.step) {
145
+ values.push(i);
146
+ if (values.length > 10000)
147
+ break; // Safety limit
148
+ }
149
+ return Promise.resolve({
150
+ success: true,
151
+ count: values.length,
152
+ values,
153
+ });
154
+ },
155
+ };
156
+ }
157
+ /**
158
+ * Create a view
159
+ */
160
+ function createCreateViewTool(adapter) {
161
+ return {
162
+ name: "sqlite_create_view",
163
+ description: "Create a view based on a SELECT query.",
164
+ group: "admin",
165
+ inputSchema: CreateViewSchema,
166
+ outputSchema: CreateTableOutputSchema,
167
+ requiredScopes: ["write"],
168
+ annotations: idempotent("Create View"),
169
+ handler: async (params, _context) => {
170
+ const input = CreateViewSchema.parse(params);
171
+ // Validate and quote view name
172
+ const viewName = sanitizeIdentifier(input.viewName);
173
+ // Basic validation that it's a SELECT
174
+ if (!input.selectQuery.trim().toUpperCase().startsWith("SELECT")) {
175
+ throw new Error("View definition must be a SELECT query");
176
+ }
177
+ // SQLite doesn't support CREATE OR REPLACE VIEW
178
+ // Use DROP IF EXISTS + CREATE VIEW pattern instead
179
+ if (input.replace) {
180
+ await adapter.executeQuery(`DROP VIEW IF EXISTS ${viewName}`);
181
+ }
182
+ const sql = `CREATE VIEW ${viewName} AS ${input.selectQuery}`;
183
+ await adapter.executeQuery(sql);
184
+ return {
185
+ success: true,
186
+ message: `View '${input.viewName}' created`,
187
+ sql,
188
+ };
189
+ },
190
+ };
191
+ }
192
+ /**
193
+ * List views
194
+ */
195
+ function createListViewsTool(adapter) {
196
+ return {
197
+ name: "sqlite_list_views",
198
+ description: "List all views in the database.",
199
+ group: "admin",
200
+ inputSchema: ListViewsSchema,
201
+ outputSchema: ListViewsOutputSchema,
202
+ requiredScopes: ["read"],
203
+ annotations: readOnly("List Views"),
204
+ handler: async (params, _context) => {
205
+ const input = ListViewsSchema.parse(params);
206
+ let sql = `SELECT name, sql FROM sqlite_master WHERE type = 'view'`;
207
+ if (input.pattern) {
208
+ const escapedPattern = input.pattern.replace(/'/g, "''");
209
+ sql += ` AND name LIKE '${escapedPattern}'`;
210
+ }
211
+ sql += ` ORDER BY name`;
212
+ const result = await adapter.executeReadQuery(sql);
213
+ let views = (result.rows ?? []).map((row) => ({
214
+ name: typeof row["name"] === "string" ? row["name"] : "",
215
+ sql: typeof row["sql"] === "string" ? row["sql"] : null,
216
+ }));
217
+ // Filter out SpatiaLite system views if requested (default: true)
218
+ if (input.excludeSystemViews) {
219
+ views = views.filter((v) => !isSpatialiteSystemView(v.name));
220
+ }
221
+ return {
222
+ success: true,
223
+ count: views.length,
224
+ views,
225
+ };
226
+ },
227
+ };
228
+ }
229
+ /**
230
+ * Drop a view
231
+ */
232
+ function createDropViewTool(adapter) {
233
+ return {
234
+ name: "sqlite_drop_view",
235
+ description: "Drop (delete) a view from the database.",
236
+ group: "admin",
237
+ inputSchema: DropViewSchema,
238
+ outputSchema: DropTableOutputSchema,
239
+ requiredScopes: ["admin"],
240
+ annotations: destructive("Drop View"),
241
+ handler: async (params, _context) => {
242
+ const input = DropViewSchema.parse(params);
243
+ // Validate and quote view name
244
+ const viewName = sanitizeIdentifier(input.viewName);
245
+ const ifExists = input.ifExists ? "IF EXISTS " : "";
246
+ const sql = `DROP VIEW ${ifExists}${viewName}`;
247
+ await adapter.executeQuery(sql);
248
+ return {
249
+ success: true,
250
+ message: `View '${input.viewName}' dropped`,
251
+ };
252
+ },
253
+ };
254
+ }
255
+ /**
256
+ * Database statistics via dbstat
257
+ */
258
+ function createDbStatTool(adapter) {
259
+ return {
260
+ name: "sqlite_dbstat",
261
+ description: "Get database storage statistics using dbstat virtual table.",
262
+ group: "admin",
263
+ inputSchema: DbStatSchema,
264
+ requiredScopes: ["read"],
265
+ annotations: readOnly("Database Stats"),
266
+ handler: async (params, _context) => {
267
+ const input = DbStatSchema.parse(params);
268
+ try {
269
+ // Summarize mode: aggregate per-table stats
270
+ if (input.summarize) {
271
+ let sql = `SELECT
272
+ name,
273
+ COUNT(*) as page_count,
274
+ SUM(payload) as total_payload,
275
+ SUM(unused) as total_unused,
276
+ SUM(ncell) as total_cells,
277
+ MAX(mx_payload) as max_payload
278
+ FROM dbstat`;
279
+ if (input.table) {
280
+ sanitizeIdentifier(input.table);
281
+ sql += ` WHERE name = '${input.table.replace(/'/g, "''")}'`;
282
+ }
283
+ sql += ` GROUP BY name ORDER BY name LIMIT ${input.limit}`;
284
+ const result = await adapter.executeReadQuery(sql);
285
+ // Filter out SpatiaLite system tables if requested
286
+ let tables = (result.rows ?? []).map((row) => ({
287
+ name: row["name"],
288
+ pageCount: row["page_count"],
289
+ totalPayload: row["total_payload"],
290
+ totalUnused: row["total_unused"],
291
+ totalCells: row["total_cells"],
292
+ maxPayload: row["max_payload"],
293
+ }));
294
+ if (input.excludeSystemTables) {
295
+ tables = tables.filter((t) => !isSpatialiteSystemTable(t.name) &&
296
+ !isSpatialiteSystemIndex(t.name) &&
297
+ // Also filter FTS5 shadow tables (e.g., articles_fts_config, articles_fts_data)
298
+ !t.name.includes("_fts_"));
299
+ }
300
+ return {
301
+ success: true,
302
+ summarized: true,
303
+ objectCount: tables.length,
304
+ objects: tables,
305
+ };
306
+ }
307
+ // Default mode: raw page-level stats
308
+ let sql = `SELECT name, path, pageno, pagetype, ncell, payload, unused, mx_payload
309
+ FROM dbstat`;
310
+ if (input.table) {
311
+ // Validate table name
312
+ sanitizeIdentifier(input.table);
313
+ // For WHERE clause, we need raw table name without quotes for string comparison
314
+ sql += ` WHERE name = '${input.table.replace(/'/g, "''")}'`;
315
+ }
316
+ sql += ` LIMIT ${input.limit}`;
317
+ const result = await adapter.executeReadQuery(sql);
318
+ // Filter out SpatiaLite system tables/indexes if requested
319
+ let stats = result.rows ?? [];
320
+ if (input.excludeSystemTables) {
321
+ stats = stats.filter((row) => !isSpatialiteSystemTable(row["name"]) &&
322
+ !isSpatialiteSystemIndex(row["name"]) &&
323
+ // Also filter FTS5 shadow tables (e.g., articles_fts_config, articles_fts_data)
324
+ !row["name"].includes("_fts_"));
325
+ }
326
+ return {
327
+ success: true,
328
+ rowCount: stats.length,
329
+ stats,
330
+ };
331
+ }
332
+ catch {
333
+ // dbstat may not be available
334
+ // Fallback to basic stats with optional table-specific estimates
335
+ const pageCountResult = await adapter.executeReadQuery("PRAGMA page_count");
336
+ const totalPageCount = pageCountResult.rows?.[0]?.["page_count"] ??
337
+ pageCountResult.rows?.[0]?.[0];
338
+ const totalPages = typeof totalPageCount === "number"
339
+ ? totalPageCount
340
+ : Number(totalPageCount);
341
+ // If a specific table is requested, provide table-specific estimate
342
+ if (input.table) {
343
+ sanitizeIdentifier(input.table);
344
+ const escapedTable = input.table.replace(/'/g, "''");
345
+ // Check if table exists
346
+ const tableCheck = await adapter.executeReadQuery(`SELECT name FROM sqlite_master WHERE type='table' AND name='${escapedTable}'`);
347
+ if (!tableCheck.rows || tableCheck.rows.length === 0) {
348
+ return {
349
+ success: false,
350
+ message: `Table '${input.table}' not found`,
351
+ };
352
+ }
353
+ // Get row count for the specific table
354
+ const countResult = await adapter.executeReadQuery(`SELECT COUNT(*) as count FROM "${input.table}"`);
355
+ const rowCount = Number(countResult.rows?.[0]?.["count"] ?? 0);
356
+ // Estimate pages: ~100 rows per page for typical data
357
+ const estimatedPages = Math.max(1, Math.ceil(rowCount / 100));
358
+ return {
359
+ success: true,
360
+ table: input.table,
361
+ rowCount,
362
+ estimatedPages,
363
+ totalDatabasePages: totalPages,
364
+ note: "dbstat virtual table not available; page count is estimated from row count (~100 rows/page)",
365
+ };
366
+ }
367
+ // Get table count for additional context
368
+ const tableCountResult = await adapter.executeReadQuery(`SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`);
369
+ const tableCount = Number(tableCountResult.rows?.[0]?.["cnt"] ?? 0);
370
+ return {
371
+ success: true,
372
+ pageCount: totalPages,
373
+ tableCount,
374
+ note: "dbstat virtual table not available, showing basic stats",
375
+ };
376
+ }
377
+ },
378
+ };
379
+ }
380
+ /**
381
+ * Vacuum database
382
+ */
383
+ function createVacuumTool(adapter) {
384
+ return {
385
+ name: "sqlite_vacuum",
386
+ description: "Rebuild the database to reclaim space and optimize structure.",
387
+ group: "admin",
388
+ inputSchema: VacuumSchema,
389
+ outputSchema: VacuumOutputSchema,
390
+ requiredScopes: ["admin"],
391
+ annotations: admin("Vacuum Database"),
392
+ handler: async (params, context) => {
393
+ const input = VacuumSchema.parse(params);
394
+ const progress = buildProgressContext(context);
395
+ // Phase 1: Starting vacuum
396
+ await sendProgress(progress, 1, 2, "Starting vacuum operation...");
397
+ let sql = "VACUUM";
398
+ if (input.into) {
399
+ // VACUUM INTO creates a compacted copy
400
+ const escapedPath = input.into.replace(/'/g, "''");
401
+ sql = `VACUUM INTO '${escapedPath}'`;
402
+ }
403
+ const start = Date.now();
404
+ await adapter.executeQuery(sql);
405
+ const duration = Date.now() - start;
406
+ // Phase 2: Complete
407
+ await sendProgress(progress, 2, 2, "Vacuum complete");
408
+ return {
409
+ success: true,
410
+ message: input.into
411
+ ? `Database vacuumed into '${input.into}'`
412
+ : "Database vacuumed",
413
+ durationMs: duration,
414
+ };
415
+ },
416
+ };
417
+ }
418
+ // =============================================================================
419
+ // New Virtual Table Tools
420
+ // =============================================================================
421
+ /**
422
+ * Check if a module is available
423
+ */
424
+ async function isModuleAvailable(adapter, moduleName) {
425
+ try {
426
+ const result = await adapter.executeReadQuery(`SELECT name FROM pragma_module_list WHERE name = '${moduleName}'`);
427
+ return (result.rows?.length ?? 0) > 0;
428
+ }
429
+ catch {
430
+ return false;
431
+ }
432
+ }
433
+ /**
434
+ * Check if any CSV module is available (standard csv or sqlite-xsv variant)
435
+ */
436
+ async function isCsvModuleAvailable(adapter) {
437
+ // Check for standard csv module
438
+ if (await isModuleAvailable(adapter, "csv")) {
439
+ return { available: true, variant: "csv" };
440
+ }
441
+ // Check for sqlite-xsv module (registers as xsv_reader)
442
+ if (await isModuleAvailable(adapter, "xsv_reader")) {
443
+ return { available: true, variant: "xsv" };
444
+ }
445
+ // Check for xsv (alternative name)
446
+ if (await isModuleAvailable(adapter, "xsv")) {
447
+ return { available: true, variant: "xsv" };
448
+ }
449
+ return { available: false, variant: null };
450
+ }
451
+ /**
452
+ * List all virtual tables
453
+ */
454
+ function createListVirtualTablesTool(adapter) {
455
+ return {
456
+ name: "sqlite_list_virtual_tables",
457
+ description: "List all virtual tables in the database.",
458
+ group: "admin",
459
+ inputSchema: ListVirtualTablesSchema,
460
+ outputSchema: z.object({
461
+ success: z.boolean(),
462
+ count: z.number(),
463
+ virtualTables: z.array(z.object({
464
+ name: z.string(),
465
+ module: z.string(),
466
+ sql: z.string(),
467
+ })),
468
+ }),
469
+ requiredScopes: ["read"],
470
+ annotations: readOnly("List Virtual Tables"),
471
+ handler: async (params, _context) => {
472
+ const input = ListVirtualTablesSchema.parse(params);
473
+ let sql = `SELECT name, sql FROM sqlite_master WHERE type = 'table' AND sql LIKE 'CREATE VIRTUAL TABLE%'`;
474
+ if (input.pattern) {
475
+ sql += ` AND name LIKE '${input.pattern.replace(/'/g, "''")}'`;
476
+ }
477
+ const result = await adapter.executeReadQuery(sql);
478
+ const virtualTables = (result.rows ?? []).map((row) => {
479
+ const sqlStr = typeof row["sql"] === "string" ? row["sql"] : "";
480
+ // Extract module name from SQL: CREATE VIRTUAL TABLE x USING module(...)
481
+ const match = /USING\s+(\w+)/i.exec(sqlStr);
482
+ return {
483
+ name: typeof row["name"] === "string" ? row["name"] : "",
484
+ module: match?.[1] ?? "unknown",
485
+ sql: sqlStr,
486
+ };
487
+ });
488
+ return {
489
+ success: true,
490
+ count: virtualTables.length,
491
+ virtualTables,
492
+ };
493
+ },
494
+ };
495
+ }
496
+ /**
497
+ * Get virtual table info
498
+ */
499
+ function createVirtualTableInfoTool(adapter) {
500
+ return {
501
+ name: "sqlite_virtual_table_info",
502
+ description: "Get metadata about a specific virtual table.",
503
+ group: "admin",
504
+ inputSchema: VirtualTableInfoSchema,
505
+ outputSchema: z.object({
506
+ success: z.boolean(),
507
+ name: z.string(),
508
+ module: z.string(),
509
+ moduleAvailable: z.boolean().optional(),
510
+ columns: z
511
+ .array(z.object({
512
+ name: z.string(),
513
+ type: z.string(),
514
+ }))
515
+ .optional(),
516
+ sql: z.string(),
517
+ note: z.string().optional(),
518
+ }),
519
+ requiredScopes: ["read"],
520
+ annotations: readOnly("Virtual Table Info"),
521
+ handler: async (params, _context) => {
522
+ const input = VirtualTableInfoSchema.parse(params);
523
+ // Validate table name (we need raw name for queries)
524
+ sanitizeIdentifier(input.tableName);
525
+ // Get the CREATE statement
526
+ const sqlResult = await adapter.executeReadQuery(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = '${input.tableName.replace(/'/g, "''")}' AND sql LIKE 'CREATE VIRTUAL TABLE%'`);
527
+ if (!sqlResult.rows || sqlResult.rows.length === 0) {
528
+ throw new Error(`Virtual table '${input.tableName}' not found`);
529
+ }
530
+ const sqlStr = typeof sqlResult.rows[0]?.["sql"] === "string"
531
+ ? sqlResult.rows[0]["sql"]
532
+ : "";
533
+ const match = /USING\s+(\w+)/i.exec(sqlStr);
534
+ const moduleName = match?.[1] ?? "unknown";
535
+ // Get column info - may fail if module is unavailable (e.g., FTS5 in WASM)
536
+ try {
537
+ const colResult = await adapter.executeReadQuery(`PRAGMA table_info("${input.tableName}")`);
538
+ const columns = (colResult.rows ?? []).map((row) => ({
539
+ name: typeof row["name"] === "string" ? row["name"] : "",
540
+ type: typeof row["type"] === "string" ? row["type"] : "TEXT",
541
+ }));
542
+ return {
543
+ success: true,
544
+ name: input.tableName,
545
+ module: moduleName,
546
+ moduleAvailable: true,
547
+ columns,
548
+ sql: sqlStr,
549
+ };
550
+ }
551
+ catch (error) {
552
+ // Module unavailable (e.g., FTS5 in WASM) - return partial info
553
+ const errMsg = error instanceof Error ? error.message : String(error);
554
+ const isModuleError = errMsg.includes("no such module") ||
555
+ errMsg.includes("unknown module");
556
+ if (isModuleError) {
557
+ return {
558
+ success: true,
559
+ name: input.tableName,
560
+ module: moduleName,
561
+ moduleAvailable: false,
562
+ sql: sqlStr,
563
+ note: `Module '${moduleName}' not available in this environment. Column info cannot be retrieved.`,
564
+ };
565
+ }
566
+ // Re-throw unexpected errors
567
+ throw error;
568
+ }
569
+ },
570
+ };
571
+ }
572
+ /**
573
+ * Drop virtual table
574
+ */
575
+ function createDropVirtualTableTool(adapter) {
576
+ return {
577
+ name: "sqlite_drop_virtual_table",
578
+ description: "Drop a virtual table.",
579
+ group: "admin",
580
+ inputSchema: DropVirtualTableSchema,
581
+ outputSchema: z.object({
582
+ success: z.boolean(),
583
+ message: z.string(),
584
+ }),
585
+ requiredScopes: ["write"],
586
+ annotations: destructive("Drop Virtual Table"),
587
+ handler: async (params, _context) => {
588
+ const input = DropVirtualTableSchema.parse(params);
589
+ // Validate and quote table name
590
+ const tableName = sanitizeIdentifier(input.tableName);
591
+ // Check if the table exists and is a virtual table
592
+ const escapedName = input.tableName.replace(/'/g, "''");
593
+ const existsResult = await adapter.executeReadQuery(`SELECT name, sql FROM sqlite_master WHERE type='table' AND name='${escapedName}'`);
594
+ const tableExists = existsResult.rows !== undefined && existsResult.rows.length > 0;
595
+ const sqlValue = existsResult.rows?.[0]?.["sql"];
596
+ const isVirtualTable = tableExists &&
597
+ typeof sqlValue === "string" &&
598
+ sqlValue.toUpperCase().includes("CREATE VIRTUAL TABLE");
599
+ // Validate that it's actually a virtual table if it exists
600
+ if (tableExists && !isVirtualTable) {
601
+ return {
602
+ success: false,
603
+ message: `'${input.tableName}' is a regular table, not a virtual table. Use sqlite_drop_table instead.`,
604
+ };
605
+ }
606
+ const sql = input.ifExists
607
+ ? `DROP TABLE IF EXISTS ${tableName}`
608
+ : `DROP TABLE ${tableName}`;
609
+ await adapter.executeWriteQuery(sql);
610
+ // Return accurate message based on whether table existed
611
+ if (tableExists) {
612
+ return {
613
+ success: true,
614
+ message: `Dropped virtual table '${input.tableName}'`,
615
+ };
616
+ }
617
+ else if (input.ifExists) {
618
+ return {
619
+ success: true,
620
+ message: `Virtual table '${input.tableName}' did not exist (no action taken)`,
621
+ };
622
+ }
623
+ else {
624
+ // This shouldn't be reached as DROP TABLE without IF EXISTS would throw
625
+ return {
626
+ success: true,
627
+ message: `Dropped virtual table '${input.tableName}'`,
628
+ };
629
+ }
630
+ },
631
+ };
632
+ }
633
+ /**
634
+ * Create CSV virtual table
635
+ */
636
+ function createCsvTableTool(adapter) {
637
+ return {
638
+ name: "sqlite_create_csv_table",
639
+ description: "Create a virtual table from a CSV file. Requires the csv extension.",
640
+ group: "admin",
641
+ inputSchema: CreateCsvTableSchema,
642
+ outputSchema: z.object({
643
+ success: z.boolean(),
644
+ message: z.string(),
645
+ sql: z.string(),
646
+ columns: z.array(z.string()),
647
+ }),
648
+ requiredScopes: ["write"],
649
+ annotations: idempotent("Create CSV Table"),
650
+ handler: async (params, _context) => {
651
+ const input = CreateCsvTableSchema.parse(params);
652
+ // Validate table name (we'll use it with double quotes in SQL)
653
+ sanitizeIdentifier(input.tableName);
654
+ // Validate that the file path is absolute (required by SQLite CSV extension)
655
+ if (!path.isAbsolute(input.filePath)) {
656
+ return {
657
+ success: false,
658
+ message: `Relative path not supported. Please use an absolute path. Example: ${path.resolve(input.filePath)}`,
659
+ sql: "",
660
+ columns: [],
661
+ };
662
+ }
663
+ // Check if csv module is available (supports standard csv and sqlite-xsv)
664
+ const { available: csvAvailable } = await isCsvModuleAvailable(adapter);
665
+ if (!csvAvailable) {
666
+ // Check if we're in WASM mode by testing for a WASM-specific limitation
667
+ const isWasm = !(await isModuleAvailable(adapter, "rtree"));
668
+ return {
669
+ success: false,
670
+ message: isWasm
671
+ ? "CSV extension not available in WASM mode. Use native SQLite with the csv extension."
672
+ : "CSV extension not available. Load the csv/xsv extension using --csv flag or set CSV_EXTENSION_PATH.",
673
+ sql: "",
674
+ columns: [],
675
+ wasmLimitation: isWasm,
676
+ };
677
+ }
678
+ // Build CREATE VIRTUAL TABLE statement
679
+ const options = [
680
+ `filename='${input.filePath.replace(/'/g, "''")}'`,
681
+ ];
682
+ if (!input.header) {
683
+ options.push("header=false");
684
+ }
685
+ if (input.delimiter !== ",") {
686
+ options.push(`delimiter='${input.delimiter}'`);
687
+ }
688
+ if (input.columns && input.columns.length > 0) {
689
+ options.push(`columns=${String(input.columns.length)}`);
690
+ }
691
+ const sql = `CREATE VIRTUAL TABLE "${input.tableName}" USING csv(${options.join(", ")})`;
692
+ await adapter.executeWriteQuery(sql);
693
+ // Get column names
694
+ const colResult = await adapter.executeReadQuery(`PRAGMA table_info("${input.tableName}")`);
695
+ const columns = (colResult.rows ?? []).map((row) => typeof row["name"] === "string" ? row["name"] : "");
696
+ return {
697
+ success: true,
698
+ message: `Created CSV virtual table '${input.tableName}'`,
699
+ sql,
700
+ columns,
701
+ };
702
+ },
703
+ };
704
+ }
705
+ /**
706
+ * Analyze CSV schema
707
+ */
708
+ function createAnalyzeCsvSchemaTool(adapter) {
709
+ return {
710
+ name: "sqlite_analyze_csv_schema",
711
+ description: "Analyze a CSV file structure and infer column types. Uses a temporary virtual table.",
712
+ group: "admin",
713
+ inputSchema: AnalyzeCsvSchemaSchema,
714
+ outputSchema: z.object({
715
+ success: z.boolean(),
716
+ hasHeader: z.boolean(),
717
+ rowCount: z.number(),
718
+ columns: z.array(z.object({
719
+ name: z.string(),
720
+ inferredType: z.string(),
721
+ nullCount: z.number(),
722
+ sampleValues: z.array(z.string()),
723
+ })),
724
+ }),
725
+ requiredScopes: ["read"],
726
+ annotations: readOnly("Analyze CSV Schema"),
727
+ handler: async (params, _context) => {
728
+ const input = AnalyzeCsvSchemaSchema.parse(params);
729
+ // Validate that the file path is absolute (required by SQLite CSV extension)
730
+ if (!path.isAbsolute(input.filePath)) {
731
+ return {
732
+ success: false,
733
+ message: `Relative path not supported. Please use an absolute path. Example: ${path.resolve(input.filePath)}`,
734
+ hasHeader: false,
735
+ rowCount: 0,
736
+ columns: [],
737
+ };
738
+ }
739
+ // Check if csv module is available (supports standard csv and sqlite-xsv)
740
+ const { available: csvAvailable } = await isCsvModuleAvailable(adapter);
741
+ if (!csvAvailable) {
742
+ // Check if we're in WASM mode by testing for a WASM-specific limitation
743
+ const isWasm = !(await isModuleAvailable(adapter, "rtree"));
744
+ return {
745
+ success: false,
746
+ message: isWasm
747
+ ? "CSV extension not available in WASM mode. Use native SQLite with the csv extension."
748
+ : "CSV extension not available. Load the csv/xsv extension using --csv flag or set CSV_EXTENSION_PATH.",
749
+ hasHeader: false,
750
+ rowCount: 0,
751
+ columns: [],
752
+ wasmLimitation: isWasm,
753
+ };
754
+ }
755
+ // Create temporary table name
756
+ const tempName = `_csv_analyze_${Date.now()}`;
757
+ try {
758
+ // Create temp virtual table
759
+ const options = [`filename='${input.filePath.replace(/'/g, "''")}'`];
760
+ if (input.delimiter !== ",") {
761
+ options.push(`delimiter='${input.delimiter}'`);
762
+ }
763
+ await adapter.executeWriteQuery(`CREATE VIRTUAL TABLE "${tempName}" USING csv(${options.join(", ")})`);
764
+ // Get columns
765
+ const colResult = await adapter.executeReadQuery(`PRAGMA table_info("${tempName}")`);
766
+ const columnNames = (colResult.rows ?? []).map((row) => {
767
+ const name = row["name"];
768
+ const cid = row["cid"];
769
+ if (typeof name === "string")
770
+ return name;
771
+ return `col${typeof cid === "number" ? cid : 0}`;
772
+ });
773
+ // Sample data
774
+ const sampleResult = await adapter.executeReadQuery(`SELECT * FROM "${tempName}" LIMIT ${input.sampleRows}`);
775
+ // Analyze each column
776
+ const columns = columnNames.map((name) => {
777
+ let nullCount = 0;
778
+ let intCount = 0;
779
+ let floatCount = 0;
780
+ const samples = [];
781
+ for (const row of sampleResult.rows ?? []) {
782
+ const val = row[name];
783
+ const strVal = typeof val === "string" ? val : JSON.stringify(val ?? "");
784
+ if (val === null || strVal === "") {
785
+ nullCount++;
786
+ }
787
+ else {
788
+ if (samples.length < 3)
789
+ samples.push(strVal);
790
+ if (/^-?\d+$/.test(strVal))
791
+ intCount++;
792
+ else if (/^-?\d+\.\d+$/.test(strVal))
793
+ floatCount++;
794
+ }
795
+ }
796
+ const total = (sampleResult.rows?.length ?? 0) - nullCount;
797
+ let inferredType = "TEXT";
798
+ if (total > 0) {
799
+ if (intCount === total)
800
+ inferredType = "INTEGER";
801
+ else if (floatCount === total || intCount + floatCount === total)
802
+ inferredType = "REAL";
803
+ }
804
+ return { name, inferredType, nullCount, sampleValues: samples };
805
+ });
806
+ // Count total rows
807
+ const countResult = await adapter.executeReadQuery(`SELECT COUNT(*) as cnt FROM "${tempName}"`);
808
+ const rowCount = typeof countResult.rows?.[0]?.["cnt"] === "number"
809
+ ? countResult.rows[0]["cnt"]
810
+ : 0;
811
+ return {
812
+ success: true,
813
+ hasHeader: true, // CSV module assumes header
814
+ rowCount,
815
+ columns,
816
+ };
817
+ }
818
+ finally {
819
+ // Clean up temp table
820
+ await adapter
821
+ .executeWriteQuery(`DROP TABLE IF EXISTS "${tempName}"`)
822
+ .catch(() => {
823
+ // Ignore cleanup errors
824
+ });
825
+ }
826
+ },
827
+ };
828
+ }
829
+ /**
830
+ * Create R-Tree virtual table
831
+ */
832
+ function createRtreeTableTool(adapter) {
833
+ return {
834
+ name: "sqlite_create_rtree_table",
835
+ description: "Create an R-Tree virtual table for spatial indexing. Supports 2-5 dimensions.",
836
+ group: "admin",
837
+ inputSchema: CreateRtreeTableSchema,
838
+ outputSchema: z.object({
839
+ success: z.boolean(),
840
+ message: z.string(),
841
+ sql: z.string(),
842
+ columns: z.array(z.string()),
843
+ }),
844
+ requiredScopes: ["write"],
845
+ annotations: idempotent("Create R-Tree Table"),
846
+ handler: async (params, _context) => {
847
+ const input = CreateRtreeTableSchema.parse(params);
848
+ // Validate table name
849
+ sanitizeIdentifier(input.tableName);
850
+ // Check if rtree module is available
851
+ const rtreeAvailable = await isModuleAvailable(adapter, "rtree");
852
+ if (!rtreeAvailable) {
853
+ return {
854
+ success: false,
855
+ message: "R-Tree extension not available. Use a SQLite build with rtree support.",
856
+ sql: "",
857
+ columns: [],
858
+ wasmLimitation: true,
859
+ };
860
+ }
861
+ // Build column list based on dimensions
862
+ const columns = [input.idColumn];
863
+ const dimNames = ["X", "Y", "Z", "W", "V"];
864
+ for (let i = 0; i < input.dimensions; i++) {
865
+ const dim = dimNames[i] ?? `D${i}`;
866
+ columns.push(`min${dim}`, `max${dim}`);
867
+ }
868
+ const sql = `CREATE VIRTUAL TABLE "${input.tableName}" USING rtree(${columns.join(", ")})`;
869
+ await adapter.executeWriteQuery(sql);
870
+ return {
871
+ success: true,
872
+ message: `Created R-Tree table '${input.tableName}' with ${input.dimensions} dimensions`,
873
+ sql,
874
+ columns,
875
+ };
876
+ },
877
+ };
878
+ }
879
+ /**
880
+ * Create persistent series table
881
+ */
882
+ function createSeriesTableTool(adapter) {
883
+ return {
884
+ name: "sqlite_create_series_table",
885
+ description: "Create a table populated with a series of numbers. Unlike generate_series, this creates a persistent table.",
886
+ group: "admin",
887
+ inputSchema: CreateSeriesTableSchema,
888
+ outputSchema: z.object({
889
+ success: z.boolean(),
890
+ message: z.string(),
891
+ rowCount: z.number(),
892
+ }),
893
+ requiredScopes: ["write"],
894
+ annotations: idempotent("Create Series Table"),
895
+ handler: async (params, _context) => {
896
+ const input = CreateSeriesTableSchema.parse(params);
897
+ // Validate and quote identifiers
898
+ const tableName = sanitizeIdentifier(input.tableName);
899
+ const columnName = sanitizeIdentifier(input.columnName);
900
+ // Create table
901
+ await adapter.executeWriteQuery(`CREATE TABLE IF NOT EXISTS ${tableName} (${columnName} INTEGER PRIMARY KEY)`);
902
+ // Insert series using generate_series if available, otherwise loop
903
+ try {
904
+ await adapter.executeWriteQuery(`INSERT OR IGNORE INTO ${tableName} (${columnName}) SELECT value FROM generate_series(${input.start}, ${input.stop}, ${input.step})`);
905
+ }
906
+ catch {
907
+ // Fallback: insert values manually
908
+ const values = [];
909
+ for (let v = input.start; v <= input.stop; v += input.step) {
910
+ values.push(v);
911
+ }
912
+ if (values.length > 0) {
913
+ const insertValues = values.map((v) => `(${v})`).join(",");
914
+ await adapter.executeWriteQuery(`INSERT OR IGNORE INTO ${tableName} (${columnName}) VALUES ${insertValues}`);
915
+ }
916
+ }
917
+ // Count rows
918
+ const countResult = await adapter.executeReadQuery(`SELECT COUNT(*) as cnt FROM ${tableName}`);
919
+ const rowCount = typeof countResult.rows?.[0]?.["cnt"] === "number"
920
+ ? countResult.rows[0]["cnt"]
921
+ : 0;
922
+ return {
923
+ success: true,
924
+ message: `Created series table '${input.tableName}' with ${rowCount} rows`,
925
+ rowCount,
926
+ };
927
+ },
928
+ };
929
+ }
930
+ //# sourceMappingURL=virtual.js.map