datapeek 0.1.12 → 0.1.14
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 +38 -9
- package/dist/cli/dev.js +225 -52
- package/dist/cli/index.js +225 -52
- package/dist/client/assets/index-CVuQr9d7.js +385 -0
- package/dist/client/assets/index-Ys_niuVH.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/client/screenshots/dark1.png +0 -0
- package/dist/client/screenshots/dark2.png +0 -0
- package/dist/client/screenshots/light1.png +0 -0
- package/dist/client/screenshots/light2.png +0 -0
- package/dist/server/dev.js +225 -52
- package/dist/server/index.js +225 -52
- package/package.json +1 -1
- package/dist/client/assets/index-BXssAVuE.css +0 -1
- package/dist/client/assets/index-DgwSpfvt.js +0 -378
package/dist/cli/index.js
CHANGED
|
@@ -59,8 +59,15 @@ connectionRoutes.post("/test", async (req, res) => {
|
|
|
59
59
|
}
|
|
60
60
|
});
|
|
61
61
|
connectionRoutes.get("/provided", (req, res) => {
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
try {
|
|
63
|
+
const connString = getProvidedConnectionString();
|
|
64
|
+
res.json({ connectionString: connString || null });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
res.status(500).json({
|
|
67
|
+
connectionString: null,
|
|
68
|
+
error: error.message || "Failed to get connection string"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
64
71
|
});
|
|
65
72
|
connectionRoutes.post("/", async (req, res) => {
|
|
66
73
|
const timeout = setTimeout(() => {
|
|
@@ -163,12 +170,17 @@ tableRoutes.get("/", async (req, res) => {
|
|
|
163
170
|
const result = await executeQuery(query);
|
|
164
171
|
res.json(result);
|
|
165
172
|
} catch (error) {
|
|
173
|
+
console.error("Error fetching tables:", error);
|
|
166
174
|
const errorMessage = error.message || "";
|
|
167
175
|
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
168
176
|
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
169
177
|
await disconnect2();
|
|
170
178
|
}
|
|
171
|
-
|
|
179
|
+
const errorDetails = error.originalError?.message || error.originalError?.info?.message || error.message || "Failed to fetch tables";
|
|
180
|
+
res.status(500).json({
|
|
181
|
+
error: error.message || "Failed to fetch tables",
|
|
182
|
+
details: errorDetails
|
|
183
|
+
});
|
|
172
184
|
}
|
|
173
185
|
});
|
|
174
186
|
tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
@@ -225,13 +237,13 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
|
225
237
|
) fk ON c.table_schema = fk.table_schema
|
|
226
238
|
AND c.table_name = fk.table_name
|
|
227
239
|
AND c.column_name = fk.column_name
|
|
228
|
-
WHERE c.table_schema =
|
|
229
|
-
AND c.table_name =
|
|
240
|
+
WHERE c.table_schema = ${dialect.param(1)}
|
|
241
|
+
AND c.table_name = ${dialect.param(2)}
|
|
230
242
|
ORDER BY c.ordinal_position
|
|
231
243
|
`;
|
|
232
244
|
const result = await executeQuery(query, [
|
|
233
|
-
{ name: "
|
|
234
|
-
{ name: "
|
|
245
|
+
{ name: "p1", value: schema },
|
|
246
|
+
{ name: "p2", value: table }
|
|
235
247
|
]);
|
|
236
248
|
res.json(result);
|
|
237
249
|
} catch (error) {
|
|
@@ -319,9 +331,9 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
319
331
|
WHERE table_schema = ${dialect.param(1)} AND table_name = ${dialect.param(2)} AND column_name IN (${placeholders})
|
|
320
332
|
`;
|
|
321
333
|
const validateParams = [
|
|
322
|
-
{ name: "
|
|
323
|
-
{ name: "
|
|
324
|
-
...columnNames.map((col) => ({ name:
|
|
334
|
+
{ name: "p1", value: schema },
|
|
335
|
+
{ name: "p2", value: table },
|
|
336
|
+
...columnNames.map((col, idx) => ({ name: `p${idx + 3}`, value: col }))
|
|
325
337
|
];
|
|
326
338
|
const validateResult = await executeQuery(validateQuery, validateParams);
|
|
327
339
|
validateResult.forEach((r) => {
|
|
@@ -361,75 +373,111 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
361
373
|
switch (operator) {
|
|
362
374
|
// Text operators
|
|
363
375
|
case "contains":
|
|
364
|
-
filterParams.push({ name: `
|
|
376
|
+
filterParams.push({ name: `p${paramIndex}`, value: `%${String(value)}%` });
|
|
365
377
|
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
366
378
|
break;
|
|
367
379
|
case "equals":
|
|
368
|
-
filterParams.push({ name: `
|
|
380
|
+
filterParams.push({ name: `p${paramIndex}`, value: String(value) });
|
|
369
381
|
condition = `${quotedColumn} = ${dialect.param(paramIndex)}`;
|
|
370
382
|
break;
|
|
371
383
|
case "startsWith":
|
|
372
|
-
filterParams.push({ name: `
|
|
384
|
+
filterParams.push({ name: `p${paramIndex}`, value: `${String(value)}%` });
|
|
373
385
|
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
374
386
|
break;
|
|
375
387
|
case "endsWith":
|
|
376
|
-
filterParams.push({ name: `
|
|
388
|
+
filterParams.push({ name: `p${paramIndex}`, value: `%${String(value)}` });
|
|
377
389
|
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
378
390
|
break;
|
|
379
391
|
case "notContains":
|
|
380
|
-
filterParams.push({ name: `
|
|
392
|
+
filterParams.push({ name: `p${paramIndex}`, value: `%${String(value)}%` });
|
|
381
393
|
condition = `${quotedColumn} NOT LIKE ${dialect.param(paramIndex)}`;
|
|
382
394
|
break;
|
|
383
395
|
// Number operators
|
|
384
396
|
case "eq":
|
|
385
|
-
|
|
397
|
+
const isNumericType = dataType && ["int", "bigint", "decimal", "numeric", "float", "double", "real", "smallint", "tinyint", "money", "smallmoney"].some((t) => dataType.toLowerCase().includes(t));
|
|
398
|
+
if (isNumericType) {
|
|
399
|
+
const numValue = Number(value);
|
|
400
|
+
if (isNaN(numValue)) {
|
|
401
|
+
console.warn(`Cannot convert filter value to number for ${columnName}, skipping`);
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
filterParams.push({ name: `p${paramIndex}`, value: numValue });
|
|
405
|
+
} else {
|
|
406
|
+
filterParams.push({ name: `p${paramIndex}`, value: String(value) });
|
|
407
|
+
}
|
|
386
408
|
condition = `${quotedColumn} = ${dialect.param(paramIndex)}`;
|
|
387
409
|
break;
|
|
388
410
|
case "gt":
|
|
389
|
-
|
|
411
|
+
const gtValue = Number(value);
|
|
412
|
+
if (isNaN(gtValue)) {
|
|
413
|
+
console.warn(`Cannot convert filter value to number for ${columnName} (gt), skipping`);
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
filterParams.push({ name: `p${paramIndex}`, value: gtValue });
|
|
390
417
|
condition = `${quotedColumn} > ${dialect.param(paramIndex)}`;
|
|
391
418
|
break;
|
|
392
419
|
case "gte":
|
|
393
|
-
|
|
420
|
+
const gteValue = Number(value);
|
|
421
|
+
if (isNaN(gteValue)) {
|
|
422
|
+
console.warn(`Cannot convert filter value to number for ${columnName} (gte), skipping`);
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
filterParams.push({ name: `p${paramIndex}`, value: gteValue });
|
|
394
426
|
condition = `${quotedColumn} >= ${dialect.param(paramIndex)}`;
|
|
395
427
|
break;
|
|
396
428
|
case "lt":
|
|
397
|
-
|
|
429
|
+
const ltValue = Number(value);
|
|
430
|
+
if (isNaN(ltValue)) {
|
|
431
|
+
console.warn(`Cannot convert filter value to number for ${columnName} (lt), skipping`);
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
filterParams.push({ name: `p${paramIndex}`, value: ltValue });
|
|
398
435
|
condition = `${quotedColumn} < ${dialect.param(paramIndex)}`;
|
|
399
436
|
break;
|
|
400
437
|
case "lte":
|
|
401
|
-
|
|
438
|
+
const lteValue = Number(value);
|
|
439
|
+
if (isNaN(lteValue)) {
|
|
440
|
+
console.warn(`Cannot convert filter value to number for ${columnName} (lte), skipping`);
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
filterParams.push({ name: `p${paramIndex}`, value: lteValue });
|
|
402
444
|
condition = `${quotedColumn} <= ${dialect.param(paramIndex)}`;
|
|
403
445
|
break;
|
|
404
446
|
case "between":
|
|
405
447
|
if (typeof value === "object" && "from" in value && "to" in value) {
|
|
406
448
|
const fromParamIndex = paramIndex;
|
|
407
449
|
const toParamIndex = paramIndex + 1;
|
|
408
|
-
|
|
409
|
-
|
|
450
|
+
const fromValue = Number(value.from);
|
|
451
|
+
const toValue = Number(value.to);
|
|
452
|
+
if (isNaN(fromValue) || isNaN(toValue)) {
|
|
453
|
+
console.warn(`Cannot convert filter values to numbers for ${columnName} (between), skipping`);
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
filterParams.push({ name: `p${fromParamIndex}`, value: fromValue });
|
|
457
|
+
filterParams.push({ name: `p${toParamIndex}`, value: toValue });
|
|
410
458
|
condition = `${quotedColumn} BETWEEN ${dialect.param(fromParamIndex)} AND ${dialect.param(toParamIndex)}`;
|
|
411
459
|
paramIndex++;
|
|
412
460
|
}
|
|
413
461
|
break;
|
|
414
462
|
// Date operators
|
|
415
463
|
case "dateEq":
|
|
416
|
-
filterParams.push({ name: `
|
|
464
|
+
filterParams.push({ name: `p${paramIndex}`, value: String(value) });
|
|
417
465
|
condition = `${dialect.castToDate(quotedColumn)} = ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
418
466
|
break;
|
|
419
467
|
case "dateAfter":
|
|
420
|
-
filterParams.push({ name: `
|
|
468
|
+
filterParams.push({ name: `p${paramIndex}`, value: String(value) });
|
|
421
469
|
condition = `${dialect.castToDate(quotedColumn)} > ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
422
470
|
break;
|
|
423
471
|
case "dateBefore":
|
|
424
|
-
filterParams.push({ name: `
|
|
472
|
+
filterParams.push({ name: `p${paramIndex}`, value: String(value) });
|
|
425
473
|
condition = `${dialect.castToDate(quotedColumn)} < ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
426
474
|
break;
|
|
427
475
|
case "dateBetween":
|
|
428
476
|
if (typeof value === "object" && "from" in value && "to" in value) {
|
|
429
477
|
const fromParamIndex = paramIndex;
|
|
430
478
|
const toParamIndex = paramIndex + 1;
|
|
431
|
-
filterParams.push({ name: `
|
|
432
|
-
filterParams.push({ name: `
|
|
479
|
+
filterParams.push({ name: `p${fromParamIndex}`, value: String(value.from) });
|
|
480
|
+
filterParams.push({ name: `p${toParamIndex}`, value: String(value.to) });
|
|
433
481
|
condition = `${dialect.castToDate(quotedColumn)} BETWEEN ${dialect.castToDate(dialect.param(fromParamIndex))} AND ${dialect.castToDate(dialect.param(toParamIndex))}`;
|
|
434
482
|
paramIndex++;
|
|
435
483
|
}
|
|
@@ -441,7 +489,7 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
441
489
|
const placeholders = [];
|
|
442
490
|
value.forEach((val, i) => {
|
|
443
491
|
const currentIndex = paramIndex + i;
|
|
444
|
-
filterParams.push({ name: `
|
|
492
|
+
filterParams.push({ name: `p${currentIndex}`, value: val });
|
|
445
493
|
placeholders.push(dialect.param(currentIndex));
|
|
446
494
|
});
|
|
447
495
|
const inOperator = operator === "in" ? "IN" : "NOT IN";
|
|
@@ -451,7 +499,7 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
451
499
|
break;
|
|
452
500
|
default:
|
|
453
501
|
console.warn(`Unknown filter operator: ${operator}, falling back to contains`);
|
|
454
|
-
filterParams.push({ name: `
|
|
502
|
+
filterParams.push({ name: `p${paramIndex}`, value: `%${String(value)}%` });
|
|
455
503
|
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
456
504
|
}
|
|
457
505
|
if (condition) {
|
|
@@ -488,9 +536,9 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
488
536
|
WHERE table_schema = ${dialect.param(1)} AND table_name = ${dialect.param(2)} AND column_name = ${dialect.param(3)}
|
|
489
537
|
`;
|
|
490
538
|
const validateResult = await executeQuery(validateQuery, [
|
|
491
|
-
{ name: "
|
|
492
|
-
{ name: "
|
|
493
|
-
{ name: "
|
|
539
|
+
{ name: "p1", value: schema },
|
|
540
|
+
{ name: "p2", value: table },
|
|
541
|
+
{ name: "p3", value: orderByColumn }
|
|
494
542
|
]);
|
|
495
543
|
if (validateResult.length === 0) {
|
|
496
544
|
orderByColumn = "";
|
|
@@ -512,8 +560,8 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
512
560
|
${!topClause ? dialect.limitOffset(0, 1) : ""}
|
|
513
561
|
`;
|
|
514
562
|
const structureResult = await executeQuery(structureQuery, [
|
|
515
|
-
{ name: "
|
|
516
|
-
{ name: "
|
|
563
|
+
{ name: "p1", value: schema },
|
|
564
|
+
{ name: "p2", value: table }
|
|
517
565
|
]);
|
|
518
566
|
if (structureResult.length > 0) {
|
|
519
567
|
orderByColumn = structureResult[0].column_name || structureResult[0].COLUMN_NAME;
|
|
@@ -545,8 +593,8 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
545
593
|
`;
|
|
546
594
|
console.log("Fetching foreign keys with query:", fkQuery);
|
|
547
595
|
const foreignKeys = await executeQuery(fkQuery, [
|
|
548
|
-
{ name: "
|
|
549
|
-
{ name: "
|
|
596
|
+
{ name: "p1", value: schema },
|
|
597
|
+
{ name: "p2", value: table }
|
|
550
598
|
]);
|
|
551
599
|
console.log(`Found ${foreignKeys.length} foreign key(s)`);
|
|
552
600
|
if (foreignKeys.length > 0) {
|
|
@@ -563,11 +611,11 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
563
611
|
WHERE ${tableConditions}
|
|
564
612
|
ORDER BY table_schema, table_name, ordinal_position
|
|
565
613
|
`;
|
|
566
|
-
const batchParams = uniqueRefTables.flatMap((tableRef) => {
|
|
614
|
+
const batchParams = uniqueRefTables.flatMap((tableRef, idx) => {
|
|
567
615
|
const [refSchema, refTable] = tableRef.split(".");
|
|
568
616
|
return [
|
|
569
|
-
{ name:
|
|
570
|
-
{ name:
|
|
617
|
+
{ name: `p${idx * 2 + 1}`, value: refSchema },
|
|
618
|
+
{ name: `p${idx * 2 + 2}`, value: refTable }
|
|
571
619
|
];
|
|
572
620
|
});
|
|
573
621
|
console.log("Fetching referenced table columns with query:", batchColumnsQuery);
|
|
@@ -800,8 +848,8 @@ tableRoutes.post("/:schema/:table/related-data", async (req, res) => {
|
|
|
800
848
|
ORDER BY ordinal_position
|
|
801
849
|
`;
|
|
802
850
|
const columns = await executeQuery(columnsQuery, [
|
|
803
|
-
{ name: "
|
|
804
|
-
{ name: "
|
|
851
|
+
{ name: "p1", value: referencedSchema },
|
|
852
|
+
{ name: "p2", value: referencedTable }
|
|
805
853
|
]);
|
|
806
854
|
const referencedColInfo = columns.find((col) => {
|
|
807
855
|
const colName = col.column_name || col.COLUMN_NAME;
|
|
@@ -843,8 +891,8 @@ tableRoutes.post("/:schema/:table/related-data", async (req, res) => {
|
|
|
843
891
|
FROM ${quotedRefSchema}.${quotedRefTable}
|
|
844
892
|
WHERE ${quotedRefColumn} IN (${placeholders})
|
|
845
893
|
`;
|
|
846
|
-
const params = ids.map((id) => ({
|
|
847
|
-
name:
|
|
894
|
+
const params = ids.map((id, idx) => ({
|
|
895
|
+
name: `p${idx + 1}`,
|
|
848
896
|
value: id
|
|
849
897
|
}));
|
|
850
898
|
const result = await executeQuery(dataQuery, params);
|
|
@@ -910,9 +958,9 @@ tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
|
910
958
|
WHERE c.table_schema = ${dialect.param(1)} AND c.table_name = ${dialect.param(2)} AND c.column_name = ${dialect.param(3)}
|
|
911
959
|
`;
|
|
912
960
|
const columnResult = await executeQuery(columnQuery, [
|
|
913
|
-
{ name: "
|
|
914
|
-
{ name: "
|
|
915
|
-
{ name: "
|
|
961
|
+
{ name: "p1", value: schema },
|
|
962
|
+
{ name: "p2", value: table },
|
|
963
|
+
{ name: "p3", value: column }
|
|
916
964
|
]);
|
|
917
965
|
if (columnResult.length === 0) {
|
|
918
966
|
return res.status(400).json({ error: "Column not found" });
|
|
@@ -939,7 +987,7 @@ tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
|
939
987
|
if (searchQuery && searchQuery.trim()) {
|
|
940
988
|
const searchCols = columnsToSelect.map((col) => `${dialect.quoteId(col)} LIKE ${dialect.param(1)}`).join(" OR ");
|
|
941
989
|
query += ` WHERE (${searchCols}) AND ${quotedRefColumn} IS NOT NULL`;
|
|
942
|
-
params.push({ name: "
|
|
990
|
+
params.push({ name: "p1", value: `%${searchQuery.trim()}%` });
|
|
943
991
|
} else {
|
|
944
992
|
query += ` WHERE ${quotedRefColumn} IS NOT NULL`;
|
|
945
993
|
}
|
|
@@ -953,8 +1001,8 @@ tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
|
953
1001
|
ORDER BY ordinal_position
|
|
954
1002
|
`;
|
|
955
1003
|
const refColumns = await executeQuery(refColumnsQuery, [
|
|
956
|
-
{ name: "
|
|
957
|
-
{ name: "
|
|
1004
|
+
{ name: "p1", value: refSchema },
|
|
1005
|
+
{ name: "p2", value: refTable }
|
|
958
1006
|
]);
|
|
959
1007
|
const preferredNames = ["name", "title", "description", "code"];
|
|
960
1008
|
for (const preferredName of preferredNames) {
|
|
@@ -987,7 +1035,7 @@ tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
|
987
1035
|
} else {
|
|
988
1036
|
query += ` WHERE ${quotedRefColumn} LIKE ${dialect.param(1)}`;
|
|
989
1037
|
}
|
|
990
|
-
params.push({ name: "
|
|
1038
|
+
params.push({ name: "p1", value: `%${searchQuery.trim()}%` });
|
|
991
1039
|
query += ` AND ${quotedRefColumn} IS NOT NULL`;
|
|
992
1040
|
} else {
|
|
993
1041
|
query += ` WHERE ${quotedRefColumn} IS NOT NULL`;
|
|
@@ -1008,7 +1056,7 @@ tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
|
1008
1056
|
if (searchQuery && searchQuery.trim()) {
|
|
1009
1057
|
const searchCols = quotedColumns.map((col) => `${dialect.tryCastToNVarChar(col)} LIKE ${dialect.param(1)}`).join(" OR ");
|
|
1010
1058
|
query += ` WHERE (${searchCols}) AND ${keyColumn} IS NOT NULL`;
|
|
1011
|
-
params.push({ name: "
|
|
1059
|
+
params.push({ name: "p1", value: `%${searchQuery.trim()}%` });
|
|
1012
1060
|
} else {
|
|
1013
1061
|
query += ` WHERE ${keyColumn} IS NOT NULL`;
|
|
1014
1062
|
}
|
|
@@ -1018,7 +1066,7 @@ tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
|
1018
1066
|
if (searchQuery && searchQuery.trim()) {
|
|
1019
1067
|
if (["varchar", "nvarchar", "char", "nchar", "text", "ntext"].some((t) => dataType.includes(t))) {
|
|
1020
1068
|
query += ` WHERE ${quotedColumn} LIKE ${dialect.param(1)}`;
|
|
1021
|
-
params.push({ name: "
|
|
1069
|
+
params.push({ name: "p1", value: `%${searchQuery.trim()}%` });
|
|
1022
1070
|
query += ` AND ${quotedColumn} IS NOT NULL`;
|
|
1023
1071
|
} else {
|
|
1024
1072
|
query += ` WHERE ${quotedColumn} IS NOT NULL`;
|
|
@@ -1041,6 +1089,131 @@ tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
|
1041
1089
|
res.status(500).json({ error: error.message || "Failed to fetch distinct values" });
|
|
1042
1090
|
}
|
|
1043
1091
|
});
|
|
1092
|
+
tableRoutes.get("/:schema/:table/reverse-foreign-keys", async (req, res) => {
|
|
1093
|
+
try {
|
|
1094
|
+
const { schema, table } = req.params;
|
|
1095
|
+
const pool = getConnection();
|
|
1096
|
+
if (!pool) {
|
|
1097
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
1098
|
+
}
|
|
1099
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
1100
|
+
if (!isConnected) {
|
|
1101
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
1102
|
+
}
|
|
1103
|
+
const dialect = getDialect();
|
|
1104
|
+
const query = `
|
|
1105
|
+
SELECT
|
|
1106
|
+
kcu1.table_schema as "referencingSchema",
|
|
1107
|
+
kcu1.table_name as "referencingTable",
|
|
1108
|
+
kcu1.column_name as "fkColumnName",
|
|
1109
|
+
kcu2.column_name as "referencedColumn"
|
|
1110
|
+
FROM information_schema.referential_constraints rc
|
|
1111
|
+
INNER JOIN information_schema.key_column_usage kcu1
|
|
1112
|
+
ON rc.constraint_catalog = kcu1.constraint_catalog
|
|
1113
|
+
AND rc.constraint_schema = kcu1.constraint_schema
|
|
1114
|
+
AND rc.constraint_name = kcu1.constraint_name
|
|
1115
|
+
INNER JOIN information_schema.key_column_usage kcu2
|
|
1116
|
+
ON rc.unique_constraint_catalog = kcu2.constraint_catalog
|
|
1117
|
+
AND rc.unique_constraint_schema = kcu2.constraint_schema
|
|
1118
|
+
AND rc.unique_constraint_name = kcu2.constraint_name
|
|
1119
|
+
AND kcu1.ordinal_position = kcu2.ordinal_position
|
|
1120
|
+
WHERE kcu2.table_schema = ${dialect.param(1)}
|
|
1121
|
+
AND kcu2.table_name = ${dialect.param(2)}
|
|
1122
|
+
ORDER BY kcu1.table_schema, kcu1.table_name, kcu1.ordinal_position
|
|
1123
|
+
`;
|
|
1124
|
+
const result = await executeQuery(query, [
|
|
1125
|
+
{ name: "p1", value: schema },
|
|
1126
|
+
{ name: "p2", value: table }
|
|
1127
|
+
]);
|
|
1128
|
+
const grouped = {};
|
|
1129
|
+
result.forEach((row) => {
|
|
1130
|
+
const referencingSchema = row.referencingSchema || row.referencing_schema;
|
|
1131
|
+
const referencingTable = row.referencingTable || row.referencing_table;
|
|
1132
|
+
const fkColumnName = row.fkColumnName || row.fk_column_name;
|
|
1133
|
+
const referencedColumn = row.referencedColumn || row.referenced_column;
|
|
1134
|
+
const key = `${referencingSchema}.${referencingTable}`;
|
|
1135
|
+
if (!grouped[key]) {
|
|
1136
|
+
grouped[key] = {
|
|
1137
|
+
referencingSchema,
|
|
1138
|
+
referencingTable,
|
|
1139
|
+
fkColumns: [],
|
|
1140
|
+
referencedColumns: []
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
grouped[key].fkColumns.push(fkColumnName);
|
|
1144
|
+
grouped[key].referencedColumns.push(referencedColumn);
|
|
1145
|
+
});
|
|
1146
|
+
const reverseFks = Object.values(grouped);
|
|
1147
|
+
res.json(reverseFks);
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
console.error("Error fetching reverse foreign keys:", error);
|
|
1150
|
+
const errorMessage = error.message || "";
|
|
1151
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
1152
|
+
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
1153
|
+
await disconnect2();
|
|
1154
|
+
}
|
|
1155
|
+
res.status(500).json({ error: error.message || "Failed to fetch reverse foreign keys" });
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
tableRoutes.post("/:schema/:table/count-related", async (req, res) => {
|
|
1159
|
+
try {
|
|
1160
|
+
const { schema, table } = req.params;
|
|
1161
|
+
const { referencingSchema, referencingTable, fkColumns, referencedColumns, primaryKeyValues } = req.body;
|
|
1162
|
+
if (!referencingSchema || !referencingTable || !fkColumns || !Array.isArray(fkColumns) || !referencedColumns || !Array.isArray(referencedColumns) || !primaryKeyValues || !Array.isArray(primaryKeyValues)) {
|
|
1163
|
+
return res.status(400).json({ error: "Missing required parameters" });
|
|
1164
|
+
}
|
|
1165
|
+
if (fkColumns.length !== referencedColumns.length || fkColumns.length !== primaryKeyValues.length) {
|
|
1166
|
+
return res.status(400).json({ error: "FK columns, referenced columns, and primary key values arrays must have the same length" });
|
|
1167
|
+
}
|
|
1168
|
+
const pool = getConnection();
|
|
1169
|
+
if (!pool) {
|
|
1170
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
1171
|
+
}
|
|
1172
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
1173
|
+
if (!isConnected) {
|
|
1174
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
1175
|
+
}
|
|
1176
|
+
const dialect = getDialect();
|
|
1177
|
+
const quotedSchema = dialect.quoteId(referencingSchema);
|
|
1178
|
+
const quotedTable = dialect.quoteId(referencingTable);
|
|
1179
|
+
const whereConditions = [];
|
|
1180
|
+
let paramIndex = 1;
|
|
1181
|
+
const params = [];
|
|
1182
|
+
fkColumns.forEach((fkCol, idx) => {
|
|
1183
|
+
const quotedCol = dialect.quoteId(fkCol);
|
|
1184
|
+
let pkValue = primaryKeyValues[idx];
|
|
1185
|
+
if (typeof pkValue === "number" && isNaN(pkValue)) {
|
|
1186
|
+
pkValue = null;
|
|
1187
|
+
}
|
|
1188
|
+
if (pkValue === "NaN" || pkValue === "nan") {
|
|
1189
|
+
pkValue = null;
|
|
1190
|
+
}
|
|
1191
|
+
if (pkValue === null || pkValue === void 0) {
|
|
1192
|
+
whereConditions.push(`${quotedCol} IS NULL`);
|
|
1193
|
+
} else {
|
|
1194
|
+
whereConditions.push(`${quotedCol} = ${dialect.param(paramIndex)}`);
|
|
1195
|
+
params.push({ name: `p${paramIndex}`, value: pkValue });
|
|
1196
|
+
paramIndex++;
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
const query = `
|
|
1200
|
+
SELECT COUNT(*) as "count"
|
|
1201
|
+
FROM ${quotedSchema}.${quotedTable}
|
|
1202
|
+
WHERE ${whereConditions.join(" AND ")}
|
|
1203
|
+
`;
|
|
1204
|
+
const result = await executeQuery(query, params);
|
|
1205
|
+
const count = result[0]?.count || result[0]?.COUNT || 0;
|
|
1206
|
+
res.json({ count: Number(count) });
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
console.error("Error counting related rows:", error);
|
|
1209
|
+
const errorMessage = error.message || "";
|
|
1210
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
1211
|
+
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
1212
|
+
await disconnect2();
|
|
1213
|
+
}
|
|
1214
|
+
res.status(500).json({ error: error.message || "Failed to count related rows" });
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1044
1217
|
|
|
1045
1218
|
// src/server/routes/query.ts
|
|
1046
1219
|
import { Router as Router3 } from "express";
|