sqlcipher-mcp-server 1.0.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +433 -210
- package/package.json +1 -1
- package/src/config/constants.js +18 -40
- package/src/definitions/prompts.js +124 -0
- package/src/definitions/tools.js +363 -0
- package/src/handlers/http-handlers.js +576 -4
- package/src/handlers/mcp-handlers.js +558 -69
- package/src/handlers/prompt-handlers.js +601 -0
- package/src/server/http-server.js +52 -2
- package/src/server/mcp-server.js +208 -95
- package/src/services/database-service.js +395 -55
- package/src/utils/database-operations.js +967 -0
- package/src/utils/detectors.js +55 -0
- package/src/utils/formatters.js +470 -64
- package/src/utils/validators.js +147 -58
- package/lib/database.js +0 -216
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Type Detection Utilities
|
|
3
|
+
* Utilities for detecting and handling encrypted vs unencrypted databases
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect if a database is encrypted or unencrypted
|
|
8
|
+
* This is a helper function that attempts to determine database type
|
|
9
|
+
* @param {string} dbPath - Path to the database file
|
|
10
|
+
* @returns {Promise<{isEncrypted: boolean, needsPassword: boolean}>}
|
|
11
|
+
*/
|
|
12
|
+
export async function detectDatabaseType(dbPath) {
|
|
13
|
+
// For now, we'll rely on the connection logic in database.js
|
|
14
|
+
// which handles both encrypted and unencrypted databases gracefully
|
|
15
|
+
// This function can be expanded in the future for more sophisticated detection
|
|
16
|
+
return {
|
|
17
|
+
isEncrypted: false, // Will be determined during connection
|
|
18
|
+
needsPassword: false // Will be determined during connection
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sanitize table/column names to prevent SQL injection
|
|
24
|
+
* @param {string} name - Table or column name
|
|
25
|
+
* @returns {string} Sanitized name
|
|
26
|
+
* @throws {Error} If name contains invalid characters
|
|
27
|
+
*/
|
|
28
|
+
export function sanitizeSqlIdentifier(name) {
|
|
29
|
+
if (!name || typeof name !== 'string') {
|
|
30
|
+
throw new Error('Invalid identifier: must be a non-empty string');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// SQLite identifiers can contain letters, digits, underscores, and dollar signs
|
|
34
|
+
// They cannot start with a digit (except for auto-generated names)
|
|
35
|
+
const validPattern = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
36
|
+
|
|
37
|
+
if (!validPattern.test(name)) {
|
|
38
|
+
throw new Error(`Invalid identifier: "${name}" contains invalid characters. Only letters, numbers, underscores, and dollar signs are allowed.`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return name;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Escape a SQL identifier for use in queries
|
|
46
|
+
* @param {string} identifier - Table or column name
|
|
47
|
+
* @returns {string} Escaped identifier wrapped in quotes
|
|
48
|
+
*/
|
|
49
|
+
export function escapeIdentifier(identifier) {
|
|
50
|
+
// Sanitize first
|
|
51
|
+
sanitizeSqlIdentifier(identifier);
|
|
52
|
+
|
|
53
|
+
// Wrap in double quotes and escape any double quotes in the identifier
|
|
54
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
55
|
+
}
|
package/src/utils/formatters.js
CHANGED
|
@@ -1,64 +1,470 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Formatting Utilities
|
|
3
|
-
* Functions for formatting query results and other data
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { QUERY_CONFIG } from '../config/constants.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Format query results as a readable string
|
|
10
|
-
* @param {Object} result - Query result object with columns, rows, and rowCount
|
|
11
|
-
* @param {string[]} result.columns - Array of column names
|
|
12
|
-
* @param {Object[]} result.rows - Array of row objects
|
|
13
|
-
* @param {number} result.rowCount - Number of rows returned
|
|
14
|
-
* @returns {string} Formatted result string
|
|
15
|
-
*/
|
|
16
|
-
export function formatQueryResults(result) {
|
|
17
|
-
const { columns, rows, rowCount } = result;
|
|
18
|
-
|
|
19
|
-
if (rowCount === 0) {
|
|
20
|
-
return `Query executed successfully. No rows returned.\nColumns: ${columns.join(', ')}`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Build table-like output
|
|
24
|
-
let output = `Query executed successfully. ${rowCount} row(s) returned.\n\n`;
|
|
25
|
-
|
|
26
|
-
// Add column headers
|
|
27
|
-
output += `Columns: ${columns.join(' | ')}\n`;
|
|
28
|
-
output += '-'.repeat(columns.join(' | ').length) + '\n';
|
|
29
|
-
|
|
30
|
-
// Add rows (limit to first maxDisplayRows for display)
|
|
31
|
-
const displayRows = rows.slice(0, QUERY_CONFIG.maxDisplayRows);
|
|
32
|
-
for (const row of displayRows) {
|
|
33
|
-
const values = columns.map(col => formatCellValue(row[col]));
|
|
34
|
-
output += values.join(' | ') + '\n';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (rows.length > QUERY_CONFIG.maxDisplayRows) {
|
|
38
|
-
output += `\n... (showing first ${QUERY_CONFIG.maxDisplayRows} of ${rowCount} rows)`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Add JSON representation for programmatic access
|
|
42
|
-
output += '\n\nJSON representation:\n';
|
|
43
|
-
output += JSON.stringify(result, null, 2);
|
|
44
|
-
|
|
45
|
-
return output;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Format a single cell value for display
|
|
50
|
-
* @param {any} value - Cell value to format
|
|
51
|
-
* @returns {string} Formatted cell value
|
|
52
|
-
*/
|
|
53
|
-
function formatCellValue(value) {
|
|
54
|
-
// Handle null/undefined
|
|
55
|
-
if (value === null || value === undefined) {
|
|
56
|
-
return 'NULL';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Convert to string and truncate long values
|
|
60
|
-
const str = String(value);
|
|
61
|
-
return str.length > QUERY_CONFIG.maxValueLength
|
|
62
|
-
? str.substring(0, QUERY_CONFIG.maxValueLength - 3) + '...'
|
|
63
|
-
: str;
|
|
64
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Formatting Utilities
|
|
3
|
+
* Functions for formatting query results and other data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { QUERY_CONFIG } from '../config/constants.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format query results as a readable string
|
|
10
|
+
* @param {Object} result - Query result object with columns, rows, and rowCount
|
|
11
|
+
* @param {string[]} result.columns - Array of column names
|
|
12
|
+
* @param {Object[]} result.rows - Array of row objects
|
|
13
|
+
* @param {number} result.rowCount - Number of rows returned
|
|
14
|
+
* @returns {string} Formatted result string
|
|
15
|
+
*/
|
|
16
|
+
export function formatQueryResults(result) {
|
|
17
|
+
const { columns, rows, rowCount } = result;
|
|
18
|
+
|
|
19
|
+
if (rowCount === 0) {
|
|
20
|
+
return `Query executed successfully. No rows returned.\nColumns: ${columns.join(', ')}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Build table-like output
|
|
24
|
+
let output = `Query executed successfully. ${rowCount} row(s) returned.\n\n`;
|
|
25
|
+
|
|
26
|
+
// Add column headers
|
|
27
|
+
output += `Columns: ${columns.join(' | ')}\n`;
|
|
28
|
+
output += '-'.repeat(columns.join(' | ').length) + '\n';
|
|
29
|
+
|
|
30
|
+
// Add rows (limit to first maxDisplayRows for display)
|
|
31
|
+
const displayRows = rows.slice(0, QUERY_CONFIG.maxDisplayRows);
|
|
32
|
+
for (const row of displayRows) {
|
|
33
|
+
const values = columns.map(col => formatCellValue(row[col]));
|
|
34
|
+
output += values.join(' | ') + '\n';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (rows.length > QUERY_CONFIG.maxDisplayRows) {
|
|
38
|
+
output += `\n... (showing first ${QUERY_CONFIG.maxDisplayRows} of ${rowCount} rows)`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Add JSON representation for programmatic access
|
|
42
|
+
output += '\n\nJSON representation:\n';
|
|
43
|
+
output += JSON.stringify(result, null, 2);
|
|
44
|
+
|
|
45
|
+
return output;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Format a single cell value for display
|
|
50
|
+
* @param {any} value - Cell value to format
|
|
51
|
+
* @returns {string} Formatted cell value
|
|
52
|
+
*/
|
|
53
|
+
function formatCellValue(value) {
|
|
54
|
+
// Handle null/undefined
|
|
55
|
+
if (value === null || value === undefined) {
|
|
56
|
+
return 'NULL';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Convert to string and truncate long values
|
|
60
|
+
const str = String(value);
|
|
61
|
+
return str.length > QUERY_CONFIG.maxValueLength
|
|
62
|
+
? str.substring(0, QUERY_CONFIG.maxValueLength - 3) + '...'
|
|
63
|
+
: str;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Format table list results
|
|
68
|
+
* @param {Array} tables - Array of table objects
|
|
69
|
+
* @returns {string} Formatted table list
|
|
70
|
+
*/
|
|
71
|
+
export function formatTableList(tables) {
|
|
72
|
+
if (!tables || tables.length === 0) {
|
|
73
|
+
return 'No tables found in database.';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let output = `Found ${tables.length} table(s):\n\n`;
|
|
77
|
+
|
|
78
|
+
for (const table of tables) {
|
|
79
|
+
output += `- ${table.name} (${table.type})`;
|
|
80
|
+
if (table.row_count !== undefined) {
|
|
81
|
+
output += ` - ${table.row_count} row(s)`;
|
|
82
|
+
}
|
|
83
|
+
output += '\n';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
output += '\n\nJSON representation:\n';
|
|
87
|
+
output += JSON.stringify(tables, null, 2);
|
|
88
|
+
|
|
89
|
+
return output;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Format table schema results
|
|
94
|
+
* @param {Object|Array} schema - Table schema or array of schemas
|
|
95
|
+
* @returns {string} Formatted schema
|
|
96
|
+
*/
|
|
97
|
+
export function formatTableSchema(schema) {
|
|
98
|
+
if (Array.isArray(schema)) {
|
|
99
|
+
// Batch result
|
|
100
|
+
let output = `Schema for ${schema.length} table(s):\n\n`;
|
|
101
|
+
|
|
102
|
+
for (const s of schema) {
|
|
103
|
+
if (s.error) {
|
|
104
|
+
output += `Table: ${s.tableName} - ERROR: ${s.error}\n\n`;
|
|
105
|
+
} else {
|
|
106
|
+
output += formatSingleTableSchema(s) + '\n\n';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
output += 'JSON representation:\n';
|
|
111
|
+
output += JSON.stringify(schema, null, 2);
|
|
112
|
+
|
|
113
|
+
return output;
|
|
114
|
+
} else {
|
|
115
|
+
// Single table
|
|
116
|
+
let output = formatSingleTableSchema(schema);
|
|
117
|
+
output += '\n\nJSON representation:\n';
|
|
118
|
+
output += JSON.stringify(schema, null, 2);
|
|
119
|
+
return output;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Format a single table schema
|
|
125
|
+
* @param {Object} schema - Table schema object
|
|
126
|
+
* @returns {string} Formatted schema
|
|
127
|
+
*/
|
|
128
|
+
function formatSingleTableSchema(schema) {
|
|
129
|
+
let output = `Table: ${schema.tableName}\n`;
|
|
130
|
+
output += '='.repeat(40) + '\n\n';
|
|
131
|
+
|
|
132
|
+
// Columns
|
|
133
|
+
output += 'Columns:\n';
|
|
134
|
+
for (const col of schema.columns) {
|
|
135
|
+
output += ` - ${col.name} (${col.type || 'UNKNOWN'})`;
|
|
136
|
+
if (col.pk) output += ' PRIMARY KEY';
|
|
137
|
+
if (col.notnull) output += ' NOT NULL';
|
|
138
|
+
if (col.dflt_value !== null) output += ` DEFAULT ${col.dflt_value}`;
|
|
139
|
+
output += '\n';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Foreign keys
|
|
143
|
+
if (schema.foreign_keys && schema.foreign_keys.length > 0) {
|
|
144
|
+
output += '\nForeign Keys:\n';
|
|
145
|
+
for (const fk of schema.foreign_keys) {
|
|
146
|
+
output += ` - ${fk.from} -> ${fk.table}.${fk.to}\n`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Indexes
|
|
151
|
+
if (schema.indexes && schema.indexes.length > 0) {
|
|
152
|
+
output += '\nIndexes:\n';
|
|
153
|
+
for (const idx of schema.indexes) {
|
|
154
|
+
output += ` - ${idx.name}`;
|
|
155
|
+
if (idx.unique) output += ' (UNIQUE)';
|
|
156
|
+
if (idx.columns && idx.columns.length > 0) {
|
|
157
|
+
const colNames = idx.columns.map(c => c.name).join(', ');
|
|
158
|
+
output += ` on (${colNames})`;
|
|
159
|
+
}
|
|
160
|
+
output += '\n';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return output;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Format foreign keys results
|
|
169
|
+
* @param {Array} foreignKeys - Array of foreign key relationships
|
|
170
|
+
* @returns {string} Formatted foreign keys
|
|
171
|
+
*/
|
|
172
|
+
export function formatForeignKeys(foreignKeys) {
|
|
173
|
+
if (!foreignKeys || foreignKeys.length === 0) {
|
|
174
|
+
return 'No foreign keys found.';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let output = `Found ${foreignKeys.length} foreign key relationship(s):\n\n`;
|
|
178
|
+
|
|
179
|
+
for (const fk of foreignKeys) {
|
|
180
|
+
output += `- ${fk.table}.${fk.from} -> ${fk.table}.${fk.to}`;
|
|
181
|
+
if (fk.on_update) output += ` ON UPDATE ${fk.on_update}`;
|
|
182
|
+
if (fk.on_delete) output += ` ON DELETE ${fk.on_delete}`;
|
|
183
|
+
output += '\n';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
output += '\n\nJSON representation:\n';
|
|
187
|
+
output += JSON.stringify(foreignKeys, null, 2);
|
|
188
|
+
|
|
189
|
+
return output;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Format indexes results
|
|
194
|
+
* @param {Array} indexes - Array of index information
|
|
195
|
+
* @returns {string} Formatted indexes
|
|
196
|
+
*/
|
|
197
|
+
export function formatIndexes(indexes) {
|
|
198
|
+
if (!indexes || indexes.length === 0) {
|
|
199
|
+
return 'No indexes found.';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let output = `Found ${indexes.length} index(es):\n\n`;
|
|
203
|
+
|
|
204
|
+
for (const idx of indexes) {
|
|
205
|
+
output += `- ${idx.name} on table ${idx.table}`;
|
|
206
|
+
if (idx.unique) output += ' (UNIQUE)';
|
|
207
|
+
if (idx.columns && idx.columns.length > 0) {
|
|
208
|
+
const colNames = idx.columns.map(c => c.name).join(', ');
|
|
209
|
+
output += ` - columns: (${colNames})`;
|
|
210
|
+
}
|
|
211
|
+
output += '\n';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
output += '\n\nJSON representation:\n';
|
|
215
|
+
output += JSON.stringify(indexes, null, 2);
|
|
216
|
+
|
|
217
|
+
return output;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Format database info results
|
|
222
|
+
* @param {Object} info - Database metadata
|
|
223
|
+
* @returns {string} Formatted database info
|
|
224
|
+
*/
|
|
225
|
+
export function formatDatabaseInfo(info) {
|
|
226
|
+
let output = 'Database Information:\n';
|
|
227
|
+
output += '='.repeat(40) + '\n\n';
|
|
228
|
+
|
|
229
|
+
if (info.path) output += `Path: ${info.path}\n`;
|
|
230
|
+
if (info.sqlite_version) output += `SQLite Version: ${info.sqlite_version}\n`;
|
|
231
|
+
if (info.size_bytes !== undefined) {
|
|
232
|
+
const sizeMB = (info.size_bytes / (1024 * 1024)).toFixed(2);
|
|
233
|
+
output += `Size: ${info.size_bytes} bytes (${sizeMB} MB)\n`;
|
|
234
|
+
}
|
|
235
|
+
if (info.page_size) output += `Page Size: ${info.page_size} bytes\n`;
|
|
236
|
+
if (info.page_count) output += `Page Count: ${info.page_count}\n`;
|
|
237
|
+
if (info.encoding) output += `Encoding: ${info.encoding}\n`;
|
|
238
|
+
if (info.user_version !== undefined) output += `User Version: ${info.user_version}\n`;
|
|
239
|
+
if (info.application_id !== undefined) output += `Application ID: ${info.application_id}\n`;
|
|
240
|
+
if (info.schema_version !== undefined) output += `Schema Version: ${info.schema_version}\n`;
|
|
241
|
+
if (info.freelist_count !== undefined) output += `Free Pages: ${info.freelist_count}\n`;
|
|
242
|
+
|
|
243
|
+
output += '\n\nJSON representation:\n';
|
|
244
|
+
output += JSON.stringify(info, null, 2);
|
|
245
|
+
|
|
246
|
+
return output;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Format table info results
|
|
251
|
+
* @param {Object} info - Table information
|
|
252
|
+
* @returns {string} Formatted table info
|
|
253
|
+
*/
|
|
254
|
+
export function formatTableInfo(info) {
|
|
255
|
+
let output = `Table Information: ${info.name}\n`;
|
|
256
|
+
output += '='.repeat(40) + '\n\n';
|
|
257
|
+
|
|
258
|
+
output += `Type: ${info.type}\n`;
|
|
259
|
+
output += `Row Count: ${info.row_count}\n`;
|
|
260
|
+
output += `Column Count: ${info.column_count}\n`;
|
|
261
|
+
|
|
262
|
+
if (info.sql) {
|
|
263
|
+
output += `\nCreate Statement:\n${info.sql}\n`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
output += '\n\nJSON representation:\n';
|
|
267
|
+
output += JSON.stringify(info, null, 2);
|
|
268
|
+
|
|
269
|
+
return output;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Format query plan results
|
|
274
|
+
* @param {Array} plan - Query execution plan
|
|
275
|
+
* @returns {string} Formatted query plan
|
|
276
|
+
*/
|
|
277
|
+
export function formatQueryPlan(plan) {
|
|
278
|
+
if (!plan || plan.length === 0) {
|
|
279
|
+
return 'No query plan available.';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let output = 'Query Execution Plan:\n';
|
|
283
|
+
output += '='.repeat(40) + '\n\n';
|
|
284
|
+
|
|
285
|
+
for (const step of plan) {
|
|
286
|
+
const indent = ' '.repeat(step.id || 0);
|
|
287
|
+
output += `${indent}${step.detail || step.notused || 'N/A'}\n`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
output += '\n\nJSON representation:\n';
|
|
291
|
+
output += JSON.stringify(plan, null, 2);
|
|
292
|
+
|
|
293
|
+
return output;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Format table statistics results
|
|
298
|
+
* @param {Object} stats - Table statistics
|
|
299
|
+
* @returns {string} Formatted statistics
|
|
300
|
+
*/
|
|
301
|
+
export function formatTableStatistics(stats) {
|
|
302
|
+
let output = `Table Statistics: ${stats.table_name}\n`;
|
|
303
|
+
output += '='.repeat(40) + '\n\n';
|
|
304
|
+
|
|
305
|
+
output += `Total Rows: ${stats.total_rows}\n`;
|
|
306
|
+
output += `Column Count: ${stats.column_count}\n\n`;
|
|
307
|
+
|
|
308
|
+
if (stats.columns && stats.columns.length > 0) {
|
|
309
|
+
output += 'Column Statistics:\n';
|
|
310
|
+
for (const col of stats.columns) {
|
|
311
|
+
output += `\n ${col.name} (${col.type}):\n`;
|
|
312
|
+
output += ` Distinct Values: ${col.distinct_count}\n`;
|
|
313
|
+
output += ` Null Count: ${col.null_count}\n`;
|
|
314
|
+
if (col.min_value !== undefined) output += ` Min: ${col.min_value}\n`;
|
|
315
|
+
if (col.max_value !== undefined) output += ` Max: ${col.max_value}\n`;
|
|
316
|
+
if (col.avg_value !== undefined) output += ` Avg: ${col.avg_value}\n`;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
output += '\n\nJSON representation:\n';
|
|
321
|
+
output += JSON.stringify(stats, null, 2);
|
|
322
|
+
|
|
323
|
+
return output;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Format sample data results
|
|
328
|
+
* @param {Object} sample - Sample data
|
|
329
|
+
* @returns {string} Formatted sample data
|
|
330
|
+
*/
|
|
331
|
+
export function formatSampleData(sample) {
|
|
332
|
+
let output = `Sample Data from ${sample.table_name}\n`;
|
|
333
|
+
output += '='.repeat(40) + '\n\n';
|
|
334
|
+
|
|
335
|
+
output += `Showing ${sample.row_count} row(s) (limit: ${sample.limit}, offset: ${sample.offset})\n\n`;
|
|
336
|
+
|
|
337
|
+
if (sample.rows && sample.rows.length > 0) {
|
|
338
|
+
// Add column headers
|
|
339
|
+
output += `Columns: ${sample.columns.join(' | ')}\n`;
|
|
340
|
+
output += '-'.repeat(sample.columns.join(' | ').length) + '\n';
|
|
341
|
+
|
|
342
|
+
// Add rows
|
|
343
|
+
for (const row of sample.rows) {
|
|
344
|
+
const values = sample.columns.map(col => formatCellValue(row[col]));
|
|
345
|
+
output += values.join(' | ') + '\n';
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
output += 'No rows in sample.\n';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
output += '\n\nJSON representation:\n';
|
|
352
|
+
output += JSON.stringify(sample, null, 2);
|
|
353
|
+
|
|
354
|
+
return output;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Format column statistics results
|
|
359
|
+
* @param {Array} stats - Array of column statistics
|
|
360
|
+
* @returns {string} Formatted column statistics
|
|
361
|
+
*/
|
|
362
|
+
export function formatColumnStatistics(stats) {
|
|
363
|
+
if (!stats || stats.length === 0) {
|
|
364
|
+
return 'No column statistics available.';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let output = `Column Statistics (${stats.length} column(s)):\n`;
|
|
368
|
+
output += '='.repeat(40) + '\n\n';
|
|
369
|
+
|
|
370
|
+
for (const col of stats) {
|
|
371
|
+
output += `Column: ${col.table_name}.${col.column_name} (${col.column_type})\n`;
|
|
372
|
+
output += ` Distinct Values: ${col.distinct_count}\n`;
|
|
373
|
+
output += ` Null Count: ${col.null_count}\n`;
|
|
374
|
+
output += ` Non-Null Count: ${col.non_null_count}\n`;
|
|
375
|
+
if (col.min_value !== undefined) output += ` Min: ${col.min_value}\n`;
|
|
376
|
+
if (col.max_value !== undefined) output += ` Max: ${col.max_value}\n`;
|
|
377
|
+
if (col.avg_value !== undefined) output += ` Avg: ${col.avg_value}\n`;
|
|
378
|
+
if (col.sample_values && col.sample_values.length > 0) {
|
|
379
|
+
output += ` Sample Values: ${col.sample_values.map(v => formatCellValue(v)).join(', ')}\n`;
|
|
380
|
+
}
|
|
381
|
+
output += '\n';
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
output += 'JSON representation:\n';
|
|
385
|
+
output += JSON.stringify(stats, null, 2);
|
|
386
|
+
|
|
387
|
+
return output;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Format search results
|
|
392
|
+
* @param {Array} results - Search results
|
|
393
|
+
* @param {string} type - Type of search ('tables' or 'columns')
|
|
394
|
+
* @returns {string} Formatted search results
|
|
395
|
+
*/
|
|
396
|
+
export function formatSearchResults(results, type) {
|
|
397
|
+
if (!results || results.length === 0) {
|
|
398
|
+
return `No ${type} found matching the pattern.`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let output = `Found ${results.length} matching ${type}:\n\n`;
|
|
402
|
+
|
|
403
|
+
if (type === 'tables') {
|
|
404
|
+
for (const table of results) {
|
|
405
|
+
output += `- ${table.name} (${table.type})\n`;
|
|
406
|
+
}
|
|
407
|
+
} else if (type === 'columns') {
|
|
408
|
+
for (const col of results) {
|
|
409
|
+
output += `- ${col.table_name}.${col.column_name} (${col.column_type})`;
|
|
410
|
+
if (col.is_primary_key) output += ' [PK]';
|
|
411
|
+
if (col.is_nullable) output += ' [NULL]';
|
|
412
|
+
output += '\n';
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
output += '\n\nJSON representation:\n';
|
|
417
|
+
output += JSON.stringify(results, null, 2);
|
|
418
|
+
|
|
419
|
+
return output;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Format related tables results
|
|
424
|
+
* @param {Object} related - Related tables information
|
|
425
|
+
* @returns {string} Formatted related tables
|
|
426
|
+
*/
|
|
427
|
+
export function formatRelatedTables(related) {
|
|
428
|
+
let output = `Related Tables for: ${related.table_name}\n`;
|
|
429
|
+
output += '='.repeat(40) + '\n\n';
|
|
430
|
+
|
|
431
|
+
if (related.references_tables && related.references_tables.length > 0) {
|
|
432
|
+
output += `References (${related.references_tables.length}):\n`;
|
|
433
|
+
for (const table of related.references_tables) {
|
|
434
|
+
output += ` - ${table}\n`;
|
|
435
|
+
}
|
|
436
|
+
output += '\n';
|
|
437
|
+
} else {
|
|
438
|
+
output += 'No outgoing references.\n\n';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (related.referenced_by_tables && related.referenced_by_tables.length > 0) {
|
|
442
|
+
output += `Referenced By (${related.referenced_by_tables.length}):\n`;
|
|
443
|
+
for (const table of related.referenced_by_tables) {
|
|
444
|
+
output += ` - ${table}\n`;
|
|
445
|
+
}
|
|
446
|
+
output += '\n';
|
|
447
|
+
} else {
|
|
448
|
+
output += 'No incoming references.\n\n';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (related.outgoing_foreign_keys && related.outgoing_foreign_keys.length > 0) {
|
|
452
|
+
output += 'Outgoing Foreign Keys:\n';
|
|
453
|
+
for (const fk of related.outgoing_foreign_keys) {
|
|
454
|
+
output += ` - ${fk.from} -> ${fk.table}.${fk.to}\n`;
|
|
455
|
+
}
|
|
456
|
+
output += '\n';
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (related.incoming_foreign_keys && related.incoming_foreign_keys.length > 0) {
|
|
460
|
+
output += 'Incoming Foreign Keys:\n';
|
|
461
|
+
for (const fk of related.incoming_foreign_keys) {
|
|
462
|
+
output += ` - ${fk.table}.${fk.from} -> ${fk.to}\n`;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
output += '\n\nJSON representation:\n';
|
|
467
|
+
output += JSON.stringify(related, null, 2);
|
|
468
|
+
|
|
469
|
+
return output;
|
|
470
|
+
}
|