datapeek 0.1.8 → 0.1.9
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/{chunk-5X2YZYYM.js → chunk-G7KSYREO.js} +43 -5
- package/dist/cli/dev.js +313 -47
- package/dist/cli/index.js +313 -47
- package/dist/cli/{mssql-D7NDIUG7.js → mssql-HI3S4B7E.js} +1 -1
- package/dist/client/assets/index-BhZ7NrhL.css +1 -0
- package/dist/client/assets/index-CydCWCD8.js +368 -0
- package/dist/client/index.html +2 -2
- package/dist/server/{chunk-XFS5KTMI.js → chunk-JYZHE6GB.js} +43 -5
- package/dist/server/dev.js +313 -47
- package/dist/server/index.js +313 -47
- package/dist/server/{mssql-UUT2IKOP.js → mssql-LLTFKNX2.js} +1 -1
- package/package.json +4 -3
- package/dist/cli/chunk-4IGBRCNN.js +0 -118
- package/dist/cli/chunk-IPR5JXB5.js +0 -108
- package/dist/cli/chunk-PTI2CUG6.js +0 -119
- package/dist/cli/mssql-2REU4IX7.js +0 -17
- package/dist/cli/mssql-4KECNFPA.js +0 -17
- package/dist/cli/mssql-5VFXLOGV.js +0 -18
- package/dist/client/assets/index-C32MC3if.js +0 -239
- package/dist/client/assets/index-piPKidzx.css +0 -1
- package/dist/server/chunk-7G2KHS5I.js +0 -116
- package/dist/server/chunk-XMPF5YCJ.js +0 -106
- package/dist/server/mssql-7KV6MCZK.js +0 -16
- package/dist/server/mssql-XRYTOVSF.js +0 -16
|
@@ -71,7 +71,23 @@ async function testConnection(config) {
|
|
|
71
71
|
async function connect(config) {
|
|
72
72
|
await disconnect();
|
|
73
73
|
const connectionConfig = typeof config === "string" ? parseConnectionString(config) : config;
|
|
74
|
-
|
|
74
|
+
const poolConfig = {
|
|
75
|
+
...connectionConfig,
|
|
76
|
+
connectionTimeout: 3e4,
|
|
77
|
+
// 30 seconds for connection
|
|
78
|
+
pool: {
|
|
79
|
+
max: 10,
|
|
80
|
+
min: 0,
|
|
81
|
+
idleTimeoutMillis: 3e4,
|
|
82
|
+
acquireTimeoutMillis: 3e4
|
|
83
|
+
},
|
|
84
|
+
options: {
|
|
85
|
+
...connectionConfig.options,
|
|
86
|
+
requestTimeout: 12e4
|
|
87
|
+
// 2 minutes for queries (increased for complex queries with JOINs)
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
pool = new sql.ConnectionPool(poolConfig);
|
|
75
91
|
try {
|
|
76
92
|
await pool.connect();
|
|
77
93
|
} catch (error) {
|
|
@@ -96,6 +112,7 @@ async function executeQuery(query, parameters) {
|
|
|
96
112
|
throw new Error("Not connected to database");
|
|
97
113
|
}
|
|
98
114
|
const request = pool.request();
|
|
115
|
+
request.timeout = 12e4;
|
|
99
116
|
if (parameters) {
|
|
100
117
|
for (const param of parameters) {
|
|
101
118
|
if (param.type) {
|
|
@@ -105,14 +122,25 @@ async function executeQuery(query, parameters) {
|
|
|
105
122
|
}
|
|
106
123
|
}
|
|
107
124
|
}
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
try {
|
|
126
|
+
const result = await request.query(query);
|
|
127
|
+
return result.recordset || [];
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
130
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try disabling foreign key displays or reducing the page size.");
|
|
131
|
+
timeoutError.code = "ETIMEOUT";
|
|
132
|
+
timeoutError.originalError = error;
|
|
133
|
+
throw timeoutError;
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
110
137
|
}
|
|
111
138
|
async function executeQueryMultiple(query, parameters) {
|
|
112
139
|
if (!pool || !pool.connected) {
|
|
113
140
|
throw new Error("Not connected to database");
|
|
114
141
|
}
|
|
115
142
|
const request = pool.request();
|
|
143
|
+
request.timeout = 12e4;
|
|
116
144
|
if (parameters) {
|
|
117
145
|
for (const param of parameters) {
|
|
118
146
|
if (param.type) {
|
|
@@ -122,8 +150,18 @@ async function executeQueryMultiple(query, parameters) {
|
|
|
122
150
|
}
|
|
123
151
|
}
|
|
124
152
|
}
|
|
125
|
-
|
|
126
|
-
|
|
153
|
+
try {
|
|
154
|
+
const result = await request.query(query);
|
|
155
|
+
return result.recordsets || [];
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
158
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try simplifying your query or reducing the result set size.");
|
|
159
|
+
timeoutError.code = "ETIMEOUT";
|
|
160
|
+
timeoutError.originalError = error;
|
|
161
|
+
throw timeoutError;
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
127
165
|
}
|
|
128
166
|
|
|
129
167
|
export {
|
package/dist/cli/dev.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
executeQueryMultiple,
|
|
8
8
|
getConnection,
|
|
9
9
|
testConnection
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-G7KSYREO.js";
|
|
11
11
|
|
|
12
12
|
// src/server/index.ts
|
|
13
13
|
import express from "express";
|
|
@@ -88,7 +88,7 @@ connectionRoutes.delete("/", async (req, res) => {
|
|
|
88
88
|
});
|
|
89
89
|
connectionRoutes.get("/status", async (req, res) => {
|
|
90
90
|
try {
|
|
91
|
-
const { getConnection: getConnection2, executeQuery: executeQuery3 } = await import("./mssql-
|
|
91
|
+
const { getConnection: getConnection2, executeQuery: executeQuery3 } = await import("./mssql-HI3S4B7E.js");
|
|
92
92
|
const pool = getConnection2();
|
|
93
93
|
if (pool && pool.connected) {
|
|
94
94
|
try {
|
|
@@ -98,7 +98,7 @@ connectionRoutes.get("/status", async (req, res) => {
|
|
|
98
98
|
} catch (error) {
|
|
99
99
|
const errorMessage = error.message || "";
|
|
100
100
|
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
101
|
-
const { disconnect: disconnect2 } = await import("./mssql-
|
|
101
|
+
const { disconnect: disconnect2 } = await import("./mssql-HI3S4B7E.js");
|
|
102
102
|
await disconnect2();
|
|
103
103
|
}
|
|
104
104
|
res.json({ connected: false });
|
|
@@ -134,7 +134,7 @@ tableRoutes.get("/", async (req, res) => {
|
|
|
134
134
|
} catch (error) {
|
|
135
135
|
const errorMessage = error.message || "";
|
|
136
136
|
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
137
|
-
const { disconnect: disconnect2 } = await import("./mssql-
|
|
137
|
+
const { disconnect: disconnect2 } = await import("./mssql-HI3S4B7E.js");
|
|
138
138
|
await disconnect2();
|
|
139
139
|
}
|
|
140
140
|
res.status(500).json({ error: error.message || "Failed to fetch tables" });
|
|
@@ -154,7 +154,10 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
|
154
154
|
c.CHARACTER_MAXIMUM_LENGTH as maxLength,
|
|
155
155
|
c.IS_NULLABLE as isNullable,
|
|
156
156
|
c.COLUMN_DEFAULT as defaultValue,
|
|
157
|
-
CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END as isPrimaryKey
|
|
157
|
+
CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END as isPrimaryKey,
|
|
158
|
+
fk.REFERENCED_TABLE_SCHEMA as referencedSchema,
|
|
159
|
+
fk.REFERENCED_TABLE_NAME as referencedTable,
|
|
160
|
+
fk.REFERENCED_COLUMN_NAME as referencedColumn
|
|
158
161
|
FROM INFORMATION_SCHEMA.COLUMNS c
|
|
159
162
|
LEFT JOIN (
|
|
160
163
|
SELECT ku.TABLE_SCHEMA, ku.TABLE_NAME, ku.COLUMN_NAME
|
|
@@ -165,6 +168,27 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
|
165
168
|
) pk ON c.TABLE_SCHEMA = pk.TABLE_SCHEMA
|
|
166
169
|
AND c.TABLE_NAME = pk.TABLE_NAME
|
|
167
170
|
AND c.COLUMN_NAME = pk.COLUMN_NAME
|
|
171
|
+
LEFT JOIN (
|
|
172
|
+
SELECT
|
|
173
|
+
kcu1.TABLE_SCHEMA,
|
|
174
|
+
kcu1.TABLE_NAME,
|
|
175
|
+
kcu1.COLUMN_NAME,
|
|
176
|
+
kcu2.TABLE_SCHEMA as REFERENCED_TABLE_SCHEMA,
|
|
177
|
+
kcu2.TABLE_NAME as REFERENCED_TABLE_NAME,
|
|
178
|
+
kcu2.COLUMN_NAME as REFERENCED_COLUMN_NAME
|
|
179
|
+
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
|
|
180
|
+
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu1
|
|
181
|
+
ON rc.CONSTRAINT_CATALOG = kcu1.CONSTRAINT_CATALOG
|
|
182
|
+
AND rc.CONSTRAINT_SCHEMA = kcu1.CONSTRAINT_SCHEMA
|
|
183
|
+
AND rc.CONSTRAINT_NAME = kcu1.CONSTRAINT_NAME
|
|
184
|
+
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu2
|
|
185
|
+
ON rc.UNIQUE_CONSTRAINT_CATALOG = kcu2.CONSTRAINT_CATALOG
|
|
186
|
+
AND rc.UNIQUE_CONSTRAINT_SCHEMA = kcu2.CONSTRAINT_SCHEMA
|
|
187
|
+
AND rc.UNIQUE_CONSTRAINT_NAME = kcu2.CONSTRAINT_NAME
|
|
188
|
+
AND kcu1.ORDINAL_POSITION = kcu2.ORDINAL_POSITION
|
|
189
|
+
) fk ON c.TABLE_SCHEMA = fk.TABLE_SCHEMA
|
|
190
|
+
AND c.TABLE_NAME = fk.TABLE_NAME
|
|
191
|
+
AND c.COLUMN_NAME = fk.COLUMN_NAME
|
|
168
192
|
WHERE c.TABLE_SCHEMA = @schema
|
|
169
193
|
AND c.TABLE_NAME = @table
|
|
170
194
|
ORDER BY c.ORDINAL_POSITION
|
|
@@ -177,7 +201,7 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
|
177
201
|
} catch (error) {
|
|
178
202
|
const errorMessage = error.message || "";
|
|
179
203
|
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
180
|
-
const { disconnect: disconnect2 } = await import("./mssql-
|
|
204
|
+
const { disconnect: disconnect2 } = await import("./mssql-HI3S4B7E.js");
|
|
181
205
|
await disconnect2();
|
|
182
206
|
}
|
|
183
207
|
res.status(500).json({ error: error.message || "Failed to fetch table structure" });
|
|
@@ -190,6 +214,7 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
190
214
|
const pageSize = Math.min(parseInt(req.query.pageSize) || 100, 1e3);
|
|
191
215
|
const sortColumn = req.query.sortColumn;
|
|
192
216
|
const sortDirection = req.query.sortDirection || "asc";
|
|
217
|
+
const fkDisplayMode = req.query.fkDisplayMode || "key-only";
|
|
193
218
|
const offset = (page - 1) * pageSize;
|
|
194
219
|
const filters = {};
|
|
195
220
|
Object.keys(req.query).forEach((key) => {
|
|
@@ -288,41 +313,179 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
288
313
|
} catch (e) {
|
|
289
314
|
}
|
|
290
315
|
}
|
|
316
|
+
const fkJoins = [];
|
|
317
|
+
const fkSelects = [];
|
|
318
|
+
const fkDisplayColumns = {};
|
|
319
|
+
if (fkDisplayMode === "key-display" || fkDisplayMode === "display-only") {
|
|
320
|
+
const fkQuery = `
|
|
321
|
+
SELECT
|
|
322
|
+
kcu1.COLUMN_NAME as fkColumnName,
|
|
323
|
+
kcu2.TABLE_SCHEMA as referencedSchema,
|
|
324
|
+
kcu2.TABLE_NAME as referencedTable,
|
|
325
|
+
kcu2.COLUMN_NAME as referencedColumn
|
|
326
|
+
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
|
|
327
|
+
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu1
|
|
328
|
+
ON rc.CONSTRAINT_CATALOG = kcu1.CONSTRAINT_CATALOG
|
|
329
|
+
AND rc.CONSTRAINT_SCHEMA = kcu1.CONSTRAINT_SCHEMA
|
|
330
|
+
AND rc.CONSTRAINT_NAME = kcu1.CONSTRAINT_NAME
|
|
331
|
+
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu2
|
|
332
|
+
ON rc.UNIQUE_CONSTRAINT_CATALOG = kcu2.CONSTRAINT_CATALOG
|
|
333
|
+
AND rc.UNIQUE_CONSTRAINT_SCHEMA = kcu2.CONSTRAINT_SCHEMA
|
|
334
|
+
AND rc.UNIQUE_CONSTRAINT_NAME = kcu2.CONSTRAINT_NAME
|
|
335
|
+
AND kcu1.ORDINAL_POSITION = kcu2.ORDINAL_POSITION
|
|
336
|
+
WHERE kcu1.TABLE_SCHEMA = @schema
|
|
337
|
+
AND kcu1.TABLE_NAME = @table
|
|
338
|
+
`;
|
|
339
|
+
console.log("Fetching foreign keys with query:", fkQuery);
|
|
340
|
+
const foreignKeys = await executeQuery(fkQuery, [
|
|
341
|
+
{ name: "schema", value: schema, type: sql.NVarChar },
|
|
342
|
+
{ name: "table", value: table, type: sql.NVarChar }
|
|
343
|
+
]);
|
|
344
|
+
console.log(`Found ${foreignKeys.length} foreign key(s)`);
|
|
345
|
+
if (foreignKeys.length > 0) {
|
|
346
|
+
const uniqueRefTables = Array.from(
|
|
347
|
+
new Set(foreignKeys.map((fk) => `${fk.referencedSchema}.${fk.referencedTable}`))
|
|
348
|
+
);
|
|
349
|
+
const tableConditions = uniqueRefTables.map((tableRef, idx) => {
|
|
350
|
+
const [refSchema, refTable] = tableRef.split(".");
|
|
351
|
+
return `(TABLE_SCHEMA = @refSchema${idx} AND TABLE_NAME = @refTable${idx})`;
|
|
352
|
+
}).join(" OR ");
|
|
353
|
+
const batchColumnsQuery = `
|
|
354
|
+
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, ORDINAL_POSITION
|
|
355
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
356
|
+
WHERE ${tableConditions}
|
|
357
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
|
|
358
|
+
`;
|
|
359
|
+
const batchParams = uniqueRefTables.flatMap((tableRef, idx) => {
|
|
360
|
+
const [refSchema, refTable] = tableRef.split(".");
|
|
361
|
+
return [
|
|
362
|
+
{ name: `refSchema${idx}`, value: refSchema, type: sql.NVarChar },
|
|
363
|
+
{ name: `refTable${idx}`, value: refTable, type: sql.NVarChar }
|
|
364
|
+
];
|
|
365
|
+
});
|
|
366
|
+
console.log("Fetching referenced table columns with query:", batchColumnsQuery);
|
|
367
|
+
console.log("Batch parameters:", batchParams.map((p) => ({ name: p.name, value: p.value })));
|
|
368
|
+
const allRefColumns = await executeQuery(batchColumnsQuery, batchParams);
|
|
369
|
+
console.log(`Found columns for ${uniqueRefTables.length} referenced table(s)`);
|
|
370
|
+
const columnsByTable = {};
|
|
371
|
+
allRefColumns.forEach((col) => {
|
|
372
|
+
const key = `${col.TABLE_SCHEMA}.${col.TABLE_NAME}`;
|
|
373
|
+
if (!columnsByTable[key]) {
|
|
374
|
+
columnsByTable[key] = [];
|
|
375
|
+
}
|
|
376
|
+
columnsByTable[key].push(col);
|
|
377
|
+
});
|
|
378
|
+
for (const fk of foreignKeys) {
|
|
379
|
+
const fkColumn = fk.fkColumnName;
|
|
380
|
+
const refSchema = fk.referencedSchema;
|
|
381
|
+
const refTable = fk.referencedTable;
|
|
382
|
+
const refColumn = fk.referencedColumn;
|
|
383
|
+
const tableKey = `${refSchema}.${refTable}`;
|
|
384
|
+
const refColumns = columnsByTable[tableKey] || [];
|
|
385
|
+
const preferredNames = ["name", "title", "description", "code"];
|
|
386
|
+
let displayColumn = null;
|
|
387
|
+
for (const preferredName of preferredNames) {
|
|
388
|
+
const found = refColumns.find(
|
|
389
|
+
(col) => col.COLUMN_NAME.toLowerCase() === preferredName.toLowerCase()
|
|
390
|
+
);
|
|
391
|
+
if (found) {
|
|
392
|
+
displayColumn = found.COLUMN_NAME;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (!displayColumn) {
|
|
397
|
+
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
398
|
+
const found = refColumns.find(
|
|
399
|
+
(col) => stringTypes.some((type) => col.DATA_TYPE.toLowerCase().includes(type))
|
|
400
|
+
);
|
|
401
|
+
if (found) {
|
|
402
|
+
displayColumn = found.COLUMN_NAME;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (displayColumn) {
|
|
406
|
+
const alias = `fk_${fkColumn}`;
|
|
407
|
+
fkJoins.push({
|
|
408
|
+
alias,
|
|
409
|
+
refSchema,
|
|
410
|
+
refTable,
|
|
411
|
+
fkColumn,
|
|
412
|
+
refColumn,
|
|
413
|
+
displayColumn
|
|
414
|
+
});
|
|
415
|
+
fkSelects.push(`${alias}.[${displayColumn}] as [${fkColumn}_display]`);
|
|
416
|
+
fkDisplayColumns[fkColumn] = displayColumn;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const baseTableAlias = "t";
|
|
422
|
+
let baseSelects = `[${baseTableAlias}].*`;
|
|
423
|
+
if (fkDisplayMode === "display-only") {
|
|
424
|
+
const fkColumnNames = fkJoins.map((fk) => fk.fkColumn);
|
|
425
|
+
if (fkColumnNames.length > 0) {
|
|
426
|
+
baseSelects = `[${baseTableAlias}].*`;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const allSelects = `${baseSelects}${fkSelects.length > 0 ? ", " + fkSelects.join(", ") : ""}`;
|
|
430
|
+
const buildJoinClauses = (tableAlias) => {
|
|
431
|
+
return fkJoins.map(
|
|
432
|
+
(fk) => `LEFT JOIN [${fk.refSchema}].[${fk.refTable}] ${fk.alias} ON [${tableAlias}].[${fk.fkColumn}] = ${fk.alias}.[${fk.refColumn}]`
|
|
433
|
+
).join("\n ");
|
|
434
|
+
};
|
|
291
435
|
let data;
|
|
292
436
|
let generatedQuery = "";
|
|
293
437
|
if (orderByColumn) {
|
|
294
|
-
generatedQuery = `SELECT * FROM [${schema}].[${table}]${whereClause ? "\n" + whereClause : ""}
|
|
295
|
-
ORDER BY [${orderByColumn}] ${orderByDirection}
|
|
296
|
-
OFFSET ${offset} ROWS
|
|
297
|
-
FETCH NEXT ${pageSize} ROWS ONLY`;
|
|
298
438
|
const dataQuery = `
|
|
299
|
-
SELECT
|
|
439
|
+
SELECT ${allSelects}
|
|
440
|
+
FROM [${schema}].[${table}] ${baseTableAlias}
|
|
441
|
+
${buildJoinClauses(baseTableAlias)}
|
|
300
442
|
${whereClause}
|
|
301
|
-
ORDER BY [${orderByColumn}] ${orderByDirection}
|
|
443
|
+
ORDER BY ${baseTableAlias}.[${orderByColumn}] ${orderByDirection}
|
|
302
444
|
OFFSET @offset ROWS
|
|
303
445
|
FETCH NEXT @pageSize ROWS ONLY
|
|
304
446
|
`;
|
|
447
|
+
generatedQuery = `SELECT ${allSelects}
|
|
448
|
+
FROM [${schema}].[${table}] ${baseTableAlias}${fkJoins.length > 0 ? "\n" + buildJoinClauses(baseTableAlias) : ""}${whereClause ? "\n" + whereClause : ""}
|
|
449
|
+
ORDER BY ${baseTableAlias}.[${orderByColumn}] ${orderByDirection}
|
|
450
|
+
OFFSET ${offset} ROWS
|
|
451
|
+
FETCH NEXT ${pageSize} ROWS ONLY`;
|
|
452
|
+
console.log("Executing SQL query:", dataQuery);
|
|
453
|
+
console.log("Query parameters:", {
|
|
454
|
+
offset,
|
|
455
|
+
pageSize,
|
|
456
|
+
filterParams: filterParams.map((p) => ({ name: p.name, value: p.value }))
|
|
457
|
+
});
|
|
305
458
|
data = await executeQuery(dataQuery, [
|
|
306
459
|
{ name: "offset", value: offset, type: sql.Int },
|
|
307
460
|
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
308
461
|
...filterParams
|
|
309
462
|
]);
|
|
310
463
|
} else {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
ORDER BY rn`;
|
|
464
|
+
const innerQuery = `
|
|
465
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as rn
|
|
466
|
+
FROM [${schema}].[${table}]
|
|
467
|
+
${whereClause}
|
|
468
|
+
`;
|
|
317
469
|
const dataQuery = `
|
|
318
|
-
SELECT
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
WHERE rn > @offset AND rn <= @offset + @pageSize
|
|
324
|
-
ORDER BY rn
|
|
470
|
+
SELECT ${allSelects}
|
|
471
|
+
FROM (${innerQuery}) ${baseTableAlias}
|
|
472
|
+
${buildJoinClauses(baseTableAlias)}
|
|
473
|
+
WHERE ${baseTableAlias}.rn > @offset AND ${baseTableAlias}.rn <= @offset + @pageSize
|
|
474
|
+
ORDER BY ${baseTableAlias}.rn
|
|
325
475
|
`;
|
|
476
|
+
generatedQuery = `SELECT ${allSelects}
|
|
477
|
+
FROM (
|
|
478
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as rn
|
|
479
|
+
FROM [${schema}].[${table}]${whereClause ? "\n " + whereClause : ""}
|
|
480
|
+
) ${baseTableAlias}${fkJoins.length > 0 ? "\n" + buildJoinClauses(baseTableAlias) : ""}
|
|
481
|
+
WHERE ${baseTableAlias}.rn > ${offset} AND ${baseTableAlias}.rn <= ${offset + pageSize}
|
|
482
|
+
ORDER BY ${baseTableAlias}.rn`;
|
|
483
|
+
console.log("Executing SQL query:", dataQuery);
|
|
484
|
+
console.log("Query parameters:", {
|
|
485
|
+
offset,
|
|
486
|
+
pageSize,
|
|
487
|
+
filterParams: filterParams.map((p) => ({ name: p.name, value: p.value }))
|
|
488
|
+
});
|
|
326
489
|
data = await executeQuery(dataQuery, [
|
|
327
490
|
{ name: "offset", value: offset, type: sql.Int },
|
|
328
491
|
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
@@ -333,6 +496,21 @@ ORDER BY rn`;
|
|
|
333
496
|
return rest;
|
|
334
497
|
});
|
|
335
498
|
}
|
|
499
|
+
if (fkDisplayMode === "display-only") {
|
|
500
|
+
const fkColumnNames = fkJoins.map((fk) => fk.fkColumn);
|
|
501
|
+
data = data.map((row) => {
|
|
502
|
+
const filteredRow = { ...row };
|
|
503
|
+
fkColumnNames.forEach((fkCol) => {
|
|
504
|
+
delete filteredRow[fkCol];
|
|
505
|
+
const displayColName = `${fkCol}_display`;
|
|
506
|
+
if (displayColName in filteredRow) {
|
|
507
|
+
filteredRow[fkCol] = filteredRow[displayColName];
|
|
508
|
+
delete filteredRow[displayColName];
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
return filteredRow;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
336
514
|
res.json({
|
|
337
515
|
data,
|
|
338
516
|
query: generatedQuery,
|
|
@@ -341,20 +519,124 @@ ORDER BY rn`;
|
|
|
341
519
|
pageSize,
|
|
342
520
|
total,
|
|
343
521
|
totalPages: Math.ceil(total / pageSize)
|
|
344
|
-
}
|
|
522
|
+
},
|
|
523
|
+
foreignKeyDisplays: fkDisplayColumns,
|
|
524
|
+
fkDisplayMode
|
|
345
525
|
});
|
|
346
526
|
} catch (error) {
|
|
347
527
|
console.error("Error fetching table data:", error);
|
|
348
528
|
const errorMessage = error.message || "";
|
|
349
529
|
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
350
|
-
const { disconnect: disconnect2 } = await import("./mssql-
|
|
530
|
+
const { disconnect: disconnect2 } = await import("./mssql-HI3S4B7E.js");
|
|
351
531
|
await disconnect2();
|
|
352
532
|
}
|
|
533
|
+
const isTimeout = error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT") || error.originalError?.code === "ETIMEOUT" || error.originalError?.code === "ESOCKET";
|
|
353
534
|
const errorDetails = error.originalError?.message || error.originalError?.info?.message || "";
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
535
|
+
if (isTimeout) {
|
|
536
|
+
res.status(408).json({
|
|
537
|
+
error: errorMessage || "Query execution timeout",
|
|
538
|
+
details: errorDetails || "The query took too long to execute. Try disabling foreign key displays or reducing the page size.",
|
|
539
|
+
timeout: true
|
|
540
|
+
});
|
|
541
|
+
} else {
|
|
542
|
+
res.status(500).json({
|
|
543
|
+
error: errorMessage,
|
|
544
|
+
details: errorDetails
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
tableRoutes.post("/:schema/:table/related-data", async (req, res) => {
|
|
550
|
+
try {
|
|
551
|
+
const { schema, table } = req.params;
|
|
552
|
+
const { foreignKeyColumn, referencedSchema, referencedTable, referencedColumn, ids } = req.body;
|
|
553
|
+
if (!foreignKeyColumn || !referencedSchema || !referencedTable || !referencedColumn || !ids || !Array.isArray(ids) || ids.length === 0) {
|
|
554
|
+
return res.status(400).json({ error: "Missing required parameters" });
|
|
555
|
+
}
|
|
556
|
+
const pool = getConnection();
|
|
557
|
+
if (!pool || !pool.connected) {
|
|
558
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
559
|
+
}
|
|
560
|
+
const columnsQuery = `
|
|
561
|
+
SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH
|
|
562
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
563
|
+
WHERE TABLE_SCHEMA = @refSchema
|
|
564
|
+
AND TABLE_NAME = @refTable
|
|
565
|
+
ORDER BY ORDINAL_POSITION
|
|
566
|
+
`;
|
|
567
|
+
const columns = await executeQuery(columnsQuery, [
|
|
568
|
+
{ name: "refSchema", value: referencedSchema, type: sql.NVarChar },
|
|
569
|
+
{ name: "refTable", value: referencedTable, type: sql.NVarChar }
|
|
570
|
+
]);
|
|
571
|
+
const referencedColInfo = columns.find(
|
|
572
|
+
(col) => col.COLUMN_NAME === referencedColumn
|
|
573
|
+
);
|
|
574
|
+
if (!referencedColInfo) {
|
|
575
|
+
return res.status(400).json({ error: `Referenced column '${referencedColumn}' not found` });
|
|
576
|
+
}
|
|
577
|
+
const getSqlType = (dataType) => {
|
|
578
|
+
const dt = dataType.toLowerCase();
|
|
579
|
+
if (dt === "int" || dt === "integer") return sql.Int;
|
|
580
|
+
if (dt === "bigint") return sql.BigInt;
|
|
581
|
+
if (dt === "smallint") return sql.SmallInt;
|
|
582
|
+
if (dt === "tinyint") return sql.TinyInt;
|
|
583
|
+
if (dt === "bit") return sql.Bit;
|
|
584
|
+
if (dt === "float" || dt === "real" || dt === "double precision") return sql.Float;
|
|
585
|
+
if (dt === "decimal" || dt === "numeric" || dt === "money" || dt === "smallmoney") return sql.Decimal(18, 0);
|
|
586
|
+
if (dt === "datetime" || dt === "datetime2" || dt === "smalldatetime") return sql.DateTime;
|
|
587
|
+
if (dt === "date") return sql.Date;
|
|
588
|
+
if (dt === "time") return sql.Time;
|
|
589
|
+
if (dt === "uniqueidentifier") return sql.UniqueIdentifier;
|
|
590
|
+
return sql.NVarChar;
|
|
591
|
+
};
|
|
592
|
+
const referencedColumnType = getSqlType(referencedColInfo.DATA_TYPE);
|
|
593
|
+
const preferredNames = ["name", "title", "description", "code"];
|
|
594
|
+
let displayColumn = null;
|
|
595
|
+
for (const preferredName of preferredNames) {
|
|
596
|
+
const found = columns.find(
|
|
597
|
+
(col) => col.COLUMN_NAME.toLowerCase() === preferredName.toLowerCase()
|
|
598
|
+
);
|
|
599
|
+
if (found) {
|
|
600
|
+
displayColumn = found.COLUMN_NAME;
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (!displayColumn) {
|
|
605
|
+
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
606
|
+
const found = columns.find(
|
|
607
|
+
(col) => stringTypes.some((type) => col.DATA_TYPE.toLowerCase().includes(type))
|
|
608
|
+
);
|
|
609
|
+
if (found) {
|
|
610
|
+
displayColumn = found.COLUMN_NAME;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const placeholders = ids.map((_, i) => `@id${i}`).join(", ");
|
|
614
|
+
const selectColumns = displayColumn ? `[${referencedColumn}], [${displayColumn}]` : `[${referencedColumn}]`;
|
|
615
|
+
const dataQuery = `
|
|
616
|
+
SELECT ${selectColumns}
|
|
617
|
+
FROM [${referencedSchema}].[${referencedTable}]
|
|
618
|
+
WHERE [${referencedColumn}] IN (${placeholders})
|
|
619
|
+
`;
|
|
620
|
+
const params = ids.map((id, i) => ({
|
|
621
|
+
name: `id${i}`,
|
|
622
|
+
value: id,
|
|
623
|
+
type: referencedColumnType
|
|
624
|
+
}));
|
|
625
|
+
const result = await executeQuery(dataQuery, params);
|
|
626
|
+
const dataMap = {};
|
|
627
|
+
result.forEach((row) => {
|
|
628
|
+
const key = String(row[referencedColumn]);
|
|
629
|
+
dataMap[key] = displayColumn ? row[displayColumn] : null;
|
|
357
630
|
});
|
|
631
|
+
res.json({ dataMap, displayColumn });
|
|
632
|
+
} catch (error) {
|
|
633
|
+
console.error("Error fetching related data:", error);
|
|
634
|
+
const errorMessage = error.message || "";
|
|
635
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
636
|
+
const { disconnect: disconnect2 } = await import("./mssql-HI3S4B7E.js");
|
|
637
|
+
await disconnect2();
|
|
638
|
+
}
|
|
639
|
+
res.status(500).json({ error: error.message || "Failed to fetch related data" });
|
|
358
640
|
}
|
|
359
641
|
});
|
|
360
642
|
|
|
@@ -367,22 +649,6 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
367
649
|
if (!sqlQuery || typeof sqlQuery !== "string") {
|
|
368
650
|
return res.status(400).json({ error: "Query is required" });
|
|
369
651
|
}
|
|
370
|
-
const trimmedQuery = sqlQuery.trim().toUpperCase();
|
|
371
|
-
if (!trimmedQuery.startsWith("SELECT")) {
|
|
372
|
-
return res.status(400).json({
|
|
373
|
-
error: "Only SELECT queries are allowed (read-only mode)"
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
const dangerousKeywords = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE", "TRUNCATE", "EXEC", "EXECUTE"];
|
|
377
|
-
const hasDangerousKeyword = dangerousKeywords.some((keyword) => {
|
|
378
|
-
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
379
|
-
return regex.test(sqlQuery);
|
|
380
|
-
});
|
|
381
|
-
if (hasDangerousKeyword) {
|
|
382
|
-
return res.status(400).json({
|
|
383
|
-
error: "Query contains prohibited keywords. Only SELECT queries are allowed."
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
652
|
const startTime = Date.now();
|
|
387
653
|
const resultSets = await executeQueryMultiple(sqlQuery);
|
|
388
654
|
const executionTime = Date.now() - startTime;
|
|
@@ -394,7 +660,7 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
394
660
|
} catch (error) {
|
|
395
661
|
const errorMessage = error.message || "";
|
|
396
662
|
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
397
|
-
const { disconnect: disconnect2 } = await import("./mssql-
|
|
663
|
+
const { disconnect: disconnect2 } = await import("./mssql-HI3S4B7E.js");
|
|
398
664
|
await disconnect2();
|
|
399
665
|
}
|
|
400
666
|
res.status(500).json({
|