@hyqf98/easy_db_mcp_server 1.0.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/README.md +202 -8
- package/dist/database/base.d.ts +79 -0
- package/dist/database/mysql.d.ts +11 -1
- package/dist/database/mysql.js +336 -18
- package/dist/database/postgresql.d.ts +11 -1
- package/dist/database/postgresql.js +460 -2
- package/dist/database/sqlite.d.ts +11 -1
- package/dist/database/sqlite.js +223 -0
- package/dist/index.js +241 -172
- package/dist/utils/file.d.ts +4 -0
- package/dist/utils/file.js +37 -0
- package/package.json +1 -1
- package/src/database/base.ts +100 -0
- package/src/database/mysql.ts +428 -19
- package/src/database/postgresql.ts +554 -2
- package/src/database/sqlite.ts +282 -0
- package/src/index.ts +310 -195
- package/src/utils/file.ts +46 -0
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import pg from 'pg';
|
|
2
|
+
import fs from 'fs';
|
|
2
3
|
import type {
|
|
3
4
|
DatabaseAdapter,
|
|
4
5
|
TableInfo,
|
|
5
6
|
TableColumn,
|
|
6
7
|
QueryResult,
|
|
8
|
+
TransactionResult,
|
|
9
|
+
BatchInsertResult,
|
|
10
|
+
BatchUpdateResult,
|
|
11
|
+
BatchUpdateOptions,
|
|
12
|
+
ExportResult,
|
|
13
|
+
ExportOptions,
|
|
14
|
+
CreateTableResult,
|
|
15
|
+
DropTableResult,
|
|
16
|
+
TableColumnDef,
|
|
17
|
+
TableStatsResult,
|
|
18
|
+
PreviewDataResult,
|
|
19
|
+
SampleDataResult,
|
|
7
20
|
} from './base.js';
|
|
8
21
|
import type { DatabaseConfig } from '../config.js';
|
|
22
|
+
import { expandTilde, formatFileSize, ensureDirectoryExists, getDefaultFilePath } from '../utils/file.js';
|
|
9
23
|
|
|
10
24
|
const { Pool } = pg;
|
|
11
25
|
|
|
@@ -87,12 +101,36 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
87
101
|
async executeQuery(sql: string, database?: string): Promise<QueryResult> {
|
|
88
102
|
if (!this.pool) throw new Error('Not connected');
|
|
89
103
|
|
|
90
|
-
// Allow queries with fully qualified table names (schema.table)
|
|
91
104
|
const trimmed = sql.trim().toUpperCase();
|
|
92
105
|
if (!trimmed.startsWith('SELECT')) {
|
|
93
106
|
throw new Error('Only SELECT queries allowed in executeQuery');
|
|
94
107
|
}
|
|
95
108
|
|
|
109
|
+
// If database is specified and different from default, use it
|
|
110
|
+
const dbName = database || this.config.database;
|
|
111
|
+
if (dbName && dbName !== this.config.database) {
|
|
112
|
+
// For PostgreSQL, we need to connect to the specific database
|
|
113
|
+
// Create a temporary pool for the different database
|
|
114
|
+
const tempPool = new Pool({
|
|
115
|
+
host: this.config.host,
|
|
116
|
+
port: this.config.port || 5432,
|
|
117
|
+
user: this.config.user,
|
|
118
|
+
password: this.config.password,
|
|
119
|
+
database: dbName,
|
|
120
|
+
max: 1,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const result = await tempPool.query(sql);
|
|
125
|
+
return {
|
|
126
|
+
rows: result.rows,
|
|
127
|
+
rowCount: result.rowCount || 0,
|
|
128
|
+
};
|
|
129
|
+
} finally {
|
|
130
|
+
await tempPool.end();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
96
134
|
const result = await this.pool.query(sql);
|
|
97
135
|
|
|
98
136
|
return {
|
|
@@ -104,7 +142,36 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
104
142
|
async executeSQL(sql: string, database?: string): Promise<QueryResult | { affectedRows: number }> {
|
|
105
143
|
if (!this.pool) throw new Error('Not connected');
|
|
106
144
|
|
|
107
|
-
//
|
|
145
|
+
// If database is specified and different from default, use it
|
|
146
|
+
const dbName = database || this.config.database;
|
|
147
|
+
if (dbName && dbName !== this.config.database) {
|
|
148
|
+
// For PostgreSQL, we need to connect to the specific database
|
|
149
|
+
// Create a temporary pool for the different database
|
|
150
|
+
const tempPool = new Pool({
|
|
151
|
+
host: this.config.host,
|
|
152
|
+
port: this.config.port || 5432,
|
|
153
|
+
user: this.config.user,
|
|
154
|
+
password: this.config.password,
|
|
155
|
+
database: dbName,
|
|
156
|
+
max: 1,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const result = await tempPool.query(sql);
|
|
161
|
+
|
|
162
|
+
if (result.rowCount !== null && result.command !== 'SELECT') {
|
|
163
|
+
return { affectedRows: result.rowCount };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
rows: result.rows,
|
|
168
|
+
rowCount: result.rowCount || 0,
|
|
169
|
+
};
|
|
170
|
+
} finally {
|
|
171
|
+
await tempPool.end();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
108
175
|
const result = await this.pool.query(sql);
|
|
109
176
|
|
|
110
177
|
if (result.rowCount !== null && result.command !== 'SELECT') {
|
|
@@ -123,4 +190,489 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
123
190
|
this.pool = undefined;
|
|
124
191
|
}
|
|
125
192
|
}
|
|
193
|
+
|
|
194
|
+
async executeTransaction(queries: string[], database?: string): Promise<TransactionResult> {
|
|
195
|
+
if (!this.pool) throw new Error('Not connected');
|
|
196
|
+
|
|
197
|
+
let client: pg.PoolClient | null = null;
|
|
198
|
+
|
|
199
|
+
// If different database, use temporary connection
|
|
200
|
+
if (database && database !== this.config.database) {
|
|
201
|
+
const tempPool = new pg.Pool({
|
|
202
|
+
host: this.config.host,
|
|
203
|
+
port: this.config.port || 5432,
|
|
204
|
+
user: this.config.user,
|
|
205
|
+
password: this.config.password,
|
|
206
|
+
database,
|
|
207
|
+
max: 1,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
client = await tempPool.connect();
|
|
212
|
+
await client.query('BEGIN');
|
|
213
|
+
|
|
214
|
+
const results: unknown[] = [];
|
|
215
|
+
let totalAffectedRows = 0;
|
|
216
|
+
|
|
217
|
+
for (const query of queries) {
|
|
218
|
+
const result = await client.query(query);
|
|
219
|
+
if (result.rowCount !== null) {
|
|
220
|
+
totalAffectedRows += result.rowCount;
|
|
221
|
+
}
|
|
222
|
+
results.push(result);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await client.query('COMMIT');
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
success: true,
|
|
229
|
+
affectedRows: totalAffectedRows,
|
|
230
|
+
results,
|
|
231
|
+
};
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (client) await client.query('ROLLBACK');
|
|
234
|
+
throw error;
|
|
235
|
+
} finally {
|
|
236
|
+
if (client) client.release();
|
|
237
|
+
await tempPool.end();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Use default pool
|
|
242
|
+
client = await this.pool.connect();
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
await client.query('BEGIN');
|
|
246
|
+
|
|
247
|
+
const results: unknown[] = [];
|
|
248
|
+
let totalAffectedRows = 0;
|
|
249
|
+
|
|
250
|
+
for (const query of queries) {
|
|
251
|
+
const result = await client.query(query);
|
|
252
|
+
if (result.rowCount !== null) {
|
|
253
|
+
totalAffectedRows += result.rowCount;
|
|
254
|
+
}
|
|
255
|
+
results.push(result);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
await client.query('COMMIT');
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
affectedRows: totalAffectedRows,
|
|
263
|
+
results,
|
|
264
|
+
};
|
|
265
|
+
} catch (error) {
|
|
266
|
+
await client.query('ROLLBACK');
|
|
267
|
+
throw error;
|
|
268
|
+
} finally {
|
|
269
|
+
client.release();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async batchInsert(
|
|
274
|
+
table: string,
|
|
275
|
+
data: Record<string, unknown>[],
|
|
276
|
+
database?: string
|
|
277
|
+
): Promise<BatchInsertResult> {
|
|
278
|
+
if (!this.pool) throw new Error('Not connected');
|
|
279
|
+
|
|
280
|
+
if (data.length === 0) {
|
|
281
|
+
return { insertedRows: 0, duplicateRows: 0 };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let pool: pg.Pool = this.pool;
|
|
285
|
+
let dbName = database || this.config.database;
|
|
286
|
+
|
|
287
|
+
// Use temporary pool for different database
|
|
288
|
+
if (database && database !== this.config.database) {
|
|
289
|
+
pool = new pg.Pool({
|
|
290
|
+
host: this.config.host,
|
|
291
|
+
port: this.config.port || 5432,
|
|
292
|
+
user: this.config.user,
|
|
293
|
+
password: this.config.password,
|
|
294
|
+
database,
|
|
295
|
+
max: 1,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const client = await pool.connect();
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
await client.query('BEGIN');
|
|
303
|
+
|
|
304
|
+
const columns = Object.keys(data[0]);
|
|
305
|
+
const columnList = columns.map((c) => pg.escapeIdentifier(c)).join(', ');
|
|
306
|
+
|
|
307
|
+
let insertedRows = 0;
|
|
308
|
+
let duplicateRows = 0;
|
|
309
|
+
|
|
310
|
+
for (const row of data) {
|
|
311
|
+
const values = columns.map((col) => row[col]);
|
|
312
|
+
const placeholders = values.map((_, i) => `$${i + 1}`).join(', ');
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const query = `INSERT INTO ${pg.escapeIdentifier(table)} (${columnList}) VALUES (${placeholders})`;
|
|
316
|
+
await client.query(query, values);
|
|
317
|
+
insertedRows++;
|
|
318
|
+
} catch (error: any) {
|
|
319
|
+
if (error.code === '23505') { // unique_violation
|
|
320
|
+
duplicateRows++;
|
|
321
|
+
} else {
|
|
322
|
+
await client.query('ROLLBACK');
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
await client.query('COMMIT');
|
|
329
|
+
|
|
330
|
+
return { insertedRows, duplicateRows };
|
|
331
|
+
} finally {
|
|
332
|
+
client.release();
|
|
333
|
+
if (pool !== this.pool) await pool.end();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async batchUpdate(
|
|
338
|
+
table: string,
|
|
339
|
+
updates: BatchUpdateOptions,
|
|
340
|
+
database?: string
|
|
341
|
+
): Promise<BatchUpdateResult> {
|
|
342
|
+
if (!this.pool) throw new Error('Not connected');
|
|
343
|
+
|
|
344
|
+
let pool: pg.Pool = this.pool;
|
|
345
|
+
|
|
346
|
+
if (database && database !== this.config.database) {
|
|
347
|
+
pool = new pg.Pool({
|
|
348
|
+
host: this.config.host,
|
|
349
|
+
port: this.config.port || 5432,
|
|
350
|
+
user: this.config.user,
|
|
351
|
+
password: this.config.password,
|
|
352
|
+
database,
|
|
353
|
+
max: 1,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const client = await pool.connect();
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const setClause = Object.entries(updates.set)
|
|
361
|
+
.map(([key, value]) => `${pg.escapeIdentifier(key)} = ${this.escapeValue(value)}`)
|
|
362
|
+
.join(', ');
|
|
363
|
+
|
|
364
|
+
const query = `UPDATE ${pg.escapeIdentifier(table)} SET ${setClause} WHERE ${updates.where}`;
|
|
365
|
+
const result = await client.query(query);
|
|
366
|
+
|
|
367
|
+
return { affectedRows: result.rowCount || 0 };
|
|
368
|
+
} finally {
|
|
369
|
+
client.release();
|
|
370
|
+
if (pool !== this.pool) await pool.end();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private escapeValue(value: unknown): string {
|
|
375
|
+
if (value === null) return 'NULL';
|
|
376
|
+
if (typeof value === 'number') return String(value);
|
|
377
|
+
if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE';
|
|
378
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async exportData(
|
|
382
|
+
table: string,
|
|
383
|
+
format: 'json' | 'csv',
|
|
384
|
+
filePath?: string,
|
|
385
|
+
options?: ExportOptions,
|
|
386
|
+
database?: string
|
|
387
|
+
): Promise<ExportResult> {
|
|
388
|
+
if (!this.pool) throw new Error('Not connected');
|
|
389
|
+
|
|
390
|
+
let pool: pg.Pool = this.pool;
|
|
391
|
+
|
|
392
|
+
if (database && database !== this.config.database) {
|
|
393
|
+
pool = new pg.Pool({
|
|
394
|
+
host: this.config.host,
|
|
395
|
+
port: this.config.port || 5432,
|
|
396
|
+
user: this.config.user,
|
|
397
|
+
password: this.config.password,
|
|
398
|
+
database,
|
|
399
|
+
max: 1,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const client = await pool.connect();
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
let query = `SELECT * FROM ${pg.escapeIdentifier(table)}`;
|
|
407
|
+
if (options?.where) {
|
|
408
|
+
query += ` WHERE ${options.where}`;
|
|
409
|
+
}
|
|
410
|
+
if (options?.limit) {
|
|
411
|
+
query += ` LIMIT ${options.limit}`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const result = await client.query(query);
|
|
415
|
+
const data = result.rows;
|
|
416
|
+
|
|
417
|
+
const targetPath = filePath
|
|
418
|
+
? expandTilde(filePath)
|
|
419
|
+
: getDefaultFilePath(table, format);
|
|
420
|
+
|
|
421
|
+
ensureDirectoryExists(targetPath);
|
|
422
|
+
|
|
423
|
+
let content: string;
|
|
424
|
+
if (format === 'json') {
|
|
425
|
+
content = JSON.stringify(data, null, 2);
|
|
426
|
+
} else {
|
|
427
|
+
if (data.length === 0) {
|
|
428
|
+
content = '';
|
|
429
|
+
} else {
|
|
430
|
+
const headers = Object.keys(data[0]);
|
|
431
|
+
const csvRows = [
|
|
432
|
+
headers.join(','),
|
|
433
|
+
...data.map((row) =>
|
|
434
|
+
headers.map((h) => {
|
|
435
|
+
const val = row[h];
|
|
436
|
+
if (val === null) return '';
|
|
437
|
+
if (typeof val === 'string') return '"' + val.replace(/"/g, '""') + '"';
|
|
438
|
+
return String(val);
|
|
439
|
+
}).join(',')
|
|
440
|
+
),
|
|
441
|
+
];
|
|
442
|
+
content = csvRows.join('\n');
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
fs.writeFileSync(targetPath, content, 'utf-8');
|
|
447
|
+
const stats = fs.statSync(targetPath);
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
success: true,
|
|
451
|
+
filePath: targetPath,
|
|
452
|
+
rowCount: data.length,
|
|
453
|
+
fileSize: formatFileSize(stats.size),
|
|
454
|
+
};
|
|
455
|
+
} finally {
|
|
456
|
+
client.release();
|
|
457
|
+
if (pool !== this.pool) await pool.end();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async createTable(table: string, columns: TableColumnDef[], database?: string): Promise<CreateTableResult> {
|
|
462
|
+
if (!this.pool) throw new Error('Not connected');
|
|
463
|
+
|
|
464
|
+
let pool: pg.Pool = this.pool;
|
|
465
|
+
|
|
466
|
+
if (database && database !== this.config.database) {
|
|
467
|
+
pool = new pg.Pool({
|
|
468
|
+
host: this.config.host,
|
|
469
|
+
port: this.config.port || 5432,
|
|
470
|
+
user: this.config.user,
|
|
471
|
+
password: this.config.password,
|
|
472
|
+
database,
|
|
473
|
+
max: 1,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const client = await pool.connect();
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
const columnDefs = columns.map((col) => {
|
|
481
|
+
let def = `${pg.escapeIdentifier(col.name)} ${col.type}`;
|
|
482
|
+
if (col.nullable === false) def += ' NOT NULL';
|
|
483
|
+
if (col.primaryKey) def += ' PRIMARY KEY';
|
|
484
|
+
if (col.defaultValue !== undefined) {
|
|
485
|
+
def += ` DEFAULT ${this.escapeValue(col.defaultValue)}`;
|
|
486
|
+
}
|
|
487
|
+
return def;
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const query = `CREATE TABLE ${pg.escapeIdentifier(table)} (${columnDefs.join(', ')})`;
|
|
491
|
+
await client.query(query);
|
|
492
|
+
|
|
493
|
+
return { success: true, tableName: table };
|
|
494
|
+
} finally {
|
|
495
|
+
client.release();
|
|
496
|
+
if (pool !== this.pool) await pool.end();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async dropTable(table: string, ifExists = false, database?: string): Promise<DropTableResult> {
|
|
501
|
+
if (!this.pool) throw new Error('Not connected');
|
|
502
|
+
|
|
503
|
+
let pool: pg.Pool = this.pool;
|
|
504
|
+
|
|
505
|
+
if (database && database !== this.config.database) {
|
|
506
|
+
pool = new pg.Pool({
|
|
507
|
+
host: this.config.host,
|
|
508
|
+
port: this.config.port || 5432,
|
|
509
|
+
user: this.config.user,
|
|
510
|
+
password: this.config.password,
|
|
511
|
+
database,
|
|
512
|
+
max: 1,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const client = await pool.connect();
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
const query = ifExists
|
|
520
|
+
? `DROP TABLE IF EXISTS ${pg.escapeIdentifier(table)}`
|
|
521
|
+
: `DROP TABLE ${pg.escapeIdentifier(table)}`;
|
|
522
|
+
|
|
523
|
+
await client.query(query);
|
|
524
|
+
|
|
525
|
+
return { success: true, tableName: table };
|
|
526
|
+
} finally {
|
|
527
|
+
client.release();
|
|
528
|
+
if (pool !== this.pool) await pool.end();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async getTableStats(table: string, database?: string): Promise<TableStatsResult> {
|
|
533
|
+
if (!this.pool) throw new Error('Not connected');
|
|
534
|
+
|
|
535
|
+
let pool: pg.Pool = this.pool;
|
|
536
|
+
|
|
537
|
+
if (database && database !== this.config.database) {
|
|
538
|
+
pool = new pg.Pool({
|
|
539
|
+
host: this.config.host,
|
|
540
|
+
port: this.config.port || 5432,
|
|
541
|
+
user: this.config.user,
|
|
542
|
+
password: this.config.password,
|
|
543
|
+
database,
|
|
544
|
+
max: 1,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const client = await pool.connect();
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
// Get row count
|
|
552
|
+
const countResult = await client.query(
|
|
553
|
+
`SELECT COUNT(*) as count FROM ${pg.escapeIdentifier(table)}`
|
|
554
|
+
);
|
|
555
|
+
const rowCount = parseInt(countResult.rows[0].count as string, 10);
|
|
556
|
+
|
|
557
|
+
// Get column count
|
|
558
|
+
const colResult = await client.query(
|
|
559
|
+
`SELECT COUNT(*) as count FROM information_schema.columns WHERE table_name = $1`,
|
|
560
|
+
[table]
|
|
561
|
+
);
|
|
562
|
+
const columns = parseInt(colResult.rows[0].count as string, 10);
|
|
563
|
+
|
|
564
|
+
// Get indexes
|
|
565
|
+
const indexResult = await client.query(
|
|
566
|
+
`SELECT indexname FROM pg_indexes WHERE tablename = $1 AND schemaname = 'public'`,
|
|
567
|
+
[table]
|
|
568
|
+
);
|
|
569
|
+
const indexes = indexResult.rows.map((row) => row.indexname as string);
|
|
570
|
+
|
|
571
|
+
// Get table size
|
|
572
|
+
const sizeResult = await client.query(
|
|
573
|
+
`SELECT pg_size_pretty(pg_total_relation_size($1::regclass)) as size`,
|
|
574
|
+
[table]
|
|
575
|
+
);
|
|
576
|
+
const size = sizeResult.rows[0].size as string;
|
|
577
|
+
|
|
578
|
+
return {
|
|
579
|
+
tableName: table,
|
|
580
|
+
rowCount,
|
|
581
|
+
columns,
|
|
582
|
+
indexes,
|
|
583
|
+
size,
|
|
584
|
+
};
|
|
585
|
+
} finally {
|
|
586
|
+
client.release();
|
|
587
|
+
if (pool !== this.pool) await pool.end();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async previewData(
|
|
592
|
+
table: string,
|
|
593
|
+
page = 1,
|
|
594
|
+
pageSize = 50,
|
|
595
|
+
orderBy?: string,
|
|
596
|
+
database?: string
|
|
597
|
+
): Promise<PreviewDataResult> {
|
|
598
|
+
if (!this.pool) throw new Error('Not connected');
|
|
599
|
+
|
|
600
|
+
let pool: pg.Pool = this.pool;
|
|
601
|
+
|
|
602
|
+
if (database && database !== this.config.database) {
|
|
603
|
+
pool = new pg.Pool({
|
|
604
|
+
host: this.config.host,
|
|
605
|
+
port: this.config.port || 5432,
|
|
606
|
+
user: this.config.user,
|
|
607
|
+
password: this.config.password,
|
|
608
|
+
database,
|
|
609
|
+
max: 1,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const client = await pool.connect();
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
// Get total row count
|
|
617
|
+
const countResult = await client.query(
|
|
618
|
+
`SELECT COUNT(*) as count FROM ${pg.escapeIdentifier(table)}`
|
|
619
|
+
);
|
|
620
|
+
const totalRows = parseInt(countResult.rows[0].count as string, 10);
|
|
621
|
+
|
|
622
|
+
const totalPages = Math.ceil(totalRows / pageSize);
|
|
623
|
+
const offset = (page - 1) * pageSize;
|
|
624
|
+
|
|
625
|
+
// Build query
|
|
626
|
+
let query = `SELECT * FROM ${pg.escapeIdentifier(table)}`;
|
|
627
|
+
if (orderBy) {
|
|
628
|
+
query += ` ORDER BY ${pg.escapeIdentifier(orderBy)}`;
|
|
629
|
+
}
|
|
630
|
+
query += ` LIMIT ${pageSize} OFFSET ${offset}`;
|
|
631
|
+
|
|
632
|
+
const result = await client.query(query);
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
rows: result.rows as Record<string, unknown>[],
|
|
636
|
+
currentPage: page,
|
|
637
|
+
totalPages,
|
|
638
|
+
totalRows,
|
|
639
|
+
};
|
|
640
|
+
} finally {
|
|
641
|
+
client.release();
|
|
642
|
+
if (pool !== this.pool) await pool.end();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async sampleData(table: string, count = 10, database?: string): Promise<SampleDataResult> {
|
|
647
|
+
if (!this.pool) throw new Error('Not connected');
|
|
648
|
+
|
|
649
|
+
let pool: pg.Pool = this.pool;
|
|
650
|
+
|
|
651
|
+
if (database && database !== this.config.database) {
|
|
652
|
+
pool = new pg.Pool({
|
|
653
|
+
host: this.config.host,
|
|
654
|
+
port: this.config.port || 5432,
|
|
655
|
+
user: this.config.user,
|
|
656
|
+
password: this.config.password,
|
|
657
|
+
database,
|
|
658
|
+
max: 1,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const client = await pool.connect();
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
const result = await client.query(
|
|
666
|
+
`SELECT * FROM ${pg.escapeIdentifier(table)} ORDER BY RANDOM() LIMIT ${count}`
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
return {
|
|
670
|
+
rows: result.rows as Record<string, unknown>[],
|
|
671
|
+
sampleCount: result.rows.length,
|
|
672
|
+
};
|
|
673
|
+
} finally {
|
|
674
|
+
client.release();
|
|
675
|
+
if (pool !== this.pool) await pool.end();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
126
678
|
}
|