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/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
- const connString = getProvidedConnectionString();
63
- res.json({ connectionString: connString || null });
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
- res.status(500).json({ error: error.message || "Failed to fetch tables" });
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 = @schema
229
- AND c.table_name = @table
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: "schema", value: schema },
234
- { name: "table", value: table }
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: "schema", value: schema },
323
- { name: "table", value: table },
324
- ...columnNames.map((col) => ({ name: "col", value: col }))
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: `filter${paramIndex}`, value: `%${String(value)}%` });
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: `filter${paramIndex}`, value: String(value) });
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: `filter${paramIndex}`, value: `${String(value)}%` });
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: `filter${paramIndex}`, value: `%${String(value)}` });
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: `filter${paramIndex}`, value: `%${String(value)}%` });
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
- filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
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
- filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
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
- filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
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
- filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
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
- filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
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
- filterParams.push({ name: `filter${fromParamIndex}`, value: Number(value.from) });
409
- filterParams.push({ name: `filter${toParamIndex}`, value: Number(value.to) });
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: `filter${paramIndex}`, value: String(value) });
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: `filter${paramIndex}`, value: String(value) });
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: `filter${paramIndex}`, value: String(value) });
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: `filter${fromParamIndex}`, value: String(value.from) });
432
- filterParams.push({ name: `filter${toParamIndex}`, value: String(value.to) });
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: `filter${currentIndex}`, value: val });
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: `filter${paramIndex}`, value: `%${String(value)}%` });
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: "schema", value: schema },
492
- { name: "table", value: table },
493
- { name: "column", value: orderByColumn }
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: "schema", value: schema },
516
- { name: "table", value: table }
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: "schema", value: schema },
549
- { name: "table", value: table }
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: "refSchema", value: refSchema },
570
- { name: "refTable", value: refTable }
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: "refSchema", value: referencedSchema },
804
- { name: "refTable", value: referencedTable }
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: "id",
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: "schema", value: schema },
914
- { name: "table", value: table },
915
- { name: "column", value: column }
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: "search", value: `%${searchQuery.trim()}%` });
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: "refSchema", value: refSchema },
957
- { name: "refTable", value: refTable }
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: "search", value: `%${searchQuery.trim()}%` });
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: "search", value: `%${searchQuery.trim()}%` });
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: "search", value: `%${searchQuery.trim()}%` });
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";