datapeek 0.1.7 → 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 +371 -50
- package/dist/cli/index.js +371 -50
- 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 +371 -50
- package/dist/server/index.js +371 -50
- 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-Bz4cddDl.css +0 -1
- package/dist/client/assets/index-DDmcmxkb.js +0 -234
- 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,13 +214,65 @@ 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;
|
|
219
|
+
const filters = {};
|
|
220
|
+
Object.keys(req.query).forEach((key) => {
|
|
221
|
+
const match = key.match(/^filter\[(.+)\]$/);
|
|
222
|
+
if (match && req.query[key] && String(req.query[key]).trim()) {
|
|
223
|
+
filters[match[1]] = String(req.query[key]).trim();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
console.log("Received filters from query:", filters);
|
|
227
|
+
console.log("All query params:", req.query);
|
|
194
228
|
const pool = getConnection();
|
|
195
229
|
if (!pool || !pool.connected) {
|
|
196
230
|
return res.status(400).json({ error: "Not connected to database" });
|
|
197
231
|
}
|
|
198
|
-
const
|
|
199
|
-
|
|
232
|
+
const filterColumns = [];
|
|
233
|
+
if (Object.keys(filters).length > 0) {
|
|
234
|
+
try {
|
|
235
|
+
const columnNames = Object.keys(filters);
|
|
236
|
+
const placeholders = columnNames.map((_, i) => `@col${i}`).join(", ");
|
|
237
|
+
const validateQuery = `
|
|
238
|
+
SELECT COLUMN_NAME
|
|
239
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
240
|
+
WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND COLUMN_NAME IN (${placeholders})
|
|
241
|
+
`;
|
|
242
|
+
const validateParams = [
|
|
243
|
+
{ name: "schema", value: schema, type: sql.NVarChar },
|
|
244
|
+
{ name: "table", value: table, type: sql.NVarChar },
|
|
245
|
+
...columnNames.map((col, i) => ({ name: `col${i}`, value: col, type: sql.NVarChar }))
|
|
246
|
+
];
|
|
247
|
+
const validateResult = await executeQuery(validateQuery, validateParams);
|
|
248
|
+
filterColumns.push(...validateResult.map((r) => r.COLUMN_NAME));
|
|
249
|
+
console.log("Validated filter columns:", filterColumns, "from filters:", filters);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
console.error("Error validating filter columns:", e);
|
|
252
|
+
console.error("Filters that failed validation:", filters);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
let whereClause = "";
|
|
256
|
+
const filterParams = [];
|
|
257
|
+
if (filterColumns.length > 0) {
|
|
258
|
+
const validFilters = filterColumns.filter((col) => {
|
|
259
|
+
const filterValue = filters[col];
|
|
260
|
+
return filterValue && filterValue.trim() !== "";
|
|
261
|
+
});
|
|
262
|
+
if (validFilters.length > 0) {
|
|
263
|
+
const whereConditions = validFilters.map((col, index) => {
|
|
264
|
+
const filterValue = filters[col];
|
|
265
|
+
filterParams.push({ name: `filter${index}`, value: `%${filterValue.trim()}%`, type: sql.NVarChar });
|
|
266
|
+
return `[${col}] LIKE @filter${index}`;
|
|
267
|
+
});
|
|
268
|
+
whereClause = `WHERE ${whereConditions.join(" AND ")}`;
|
|
269
|
+
console.log("Applying WHERE clause:", whereClause);
|
|
270
|
+
console.log("Filter params:", filterParams);
|
|
271
|
+
console.log("Valid filters:", validFilters);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const countQuery = `SELECT COUNT(*) as total FROM [${schema}].[${table}]${whereClause ? " " + whereClause : ""}`;
|
|
275
|
+
const countResult = await executeQuery(countQuery, filterParams.length > 0 ? filterParams : []);
|
|
200
276
|
const total = countResult[0]?.total || 0;
|
|
201
277
|
let orderByColumn = sortColumn || "";
|
|
202
278
|
let orderByDirection = sortDirection?.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
@@ -237,47 +313,204 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
237
313
|
} catch (e) {
|
|
238
314
|
}
|
|
239
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
|
+
};
|
|
240
435
|
let data;
|
|
241
436
|
let generatedQuery = "";
|
|
242
437
|
if (orderByColumn) {
|
|
243
|
-
generatedQuery = `SELECT * FROM [${schema}].[${table}]
|
|
244
|
-
ORDER BY [${orderByColumn}] ${orderByDirection}
|
|
245
|
-
OFFSET ${offset} ROWS
|
|
246
|
-
FETCH NEXT ${pageSize} ROWS ONLY`;
|
|
247
438
|
const dataQuery = `
|
|
248
|
-
SELECT
|
|
249
|
-
|
|
439
|
+
SELECT ${allSelects}
|
|
440
|
+
FROM [${schema}].[${table}] ${baseTableAlias}
|
|
441
|
+
${buildJoinClauses(baseTableAlias)}
|
|
442
|
+
${whereClause}
|
|
443
|
+
ORDER BY ${baseTableAlias}.[${orderByColumn}] ${orderByDirection}
|
|
250
444
|
OFFSET @offset ROWS
|
|
251
445
|
FETCH NEXT @pageSize ROWS ONLY
|
|
252
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
|
+
});
|
|
253
458
|
data = await executeQuery(dataQuery, [
|
|
254
459
|
{ name: "offset", value: offset, type: sql.Int },
|
|
255
|
-
{ name: "pageSize", value: pageSize, type: sql.Int }
|
|
460
|
+
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
461
|
+
...filterParams
|
|
256
462
|
]);
|
|
257
463
|
} else {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
ORDER BY rn`;
|
|
464
|
+
const innerQuery = `
|
|
465
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as rn
|
|
466
|
+
FROM [${schema}].[${table}]
|
|
467
|
+
${whereClause}
|
|
468
|
+
`;
|
|
264
469
|
const dataQuery = `
|
|
265
|
-
SELECT
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
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
|
|
271
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
|
+
});
|
|
272
489
|
data = await executeQuery(dataQuery, [
|
|
273
490
|
{ name: "offset", value: offset, type: sql.Int },
|
|
274
|
-
{ name: "pageSize", value: pageSize, type: sql.Int }
|
|
491
|
+
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
492
|
+
...filterParams
|
|
275
493
|
]);
|
|
276
494
|
data = data.map((row) => {
|
|
277
495
|
const { rn, ...rest } = row;
|
|
278
496
|
return rest;
|
|
279
497
|
});
|
|
280
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
|
+
}
|
|
281
514
|
res.json({
|
|
282
515
|
data,
|
|
283
516
|
query: generatedQuery,
|
|
@@ -286,20 +519,124 @@ ORDER BY rn`;
|
|
|
286
519
|
pageSize,
|
|
287
520
|
total,
|
|
288
521
|
totalPages: Math.ceil(total / pageSize)
|
|
289
|
-
}
|
|
522
|
+
},
|
|
523
|
+
foreignKeyDisplays: fkDisplayColumns,
|
|
524
|
+
fkDisplayMode
|
|
290
525
|
});
|
|
291
526
|
} catch (error) {
|
|
292
527
|
console.error("Error fetching table data:", error);
|
|
293
528
|
const errorMessage = error.message || "";
|
|
294
529
|
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
295
|
-
const { disconnect: disconnect2 } = await import("./mssql-
|
|
530
|
+
const { disconnect: disconnect2 } = await import("./mssql-HI3S4B7E.js");
|
|
296
531
|
await disconnect2();
|
|
297
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";
|
|
298
534
|
const errorDetails = error.originalError?.message || error.originalError?.info?.message || "";
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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;
|
|
302
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" });
|
|
303
640
|
}
|
|
304
641
|
});
|
|
305
642
|
|
|
@@ -312,22 +649,6 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
312
649
|
if (!sqlQuery || typeof sqlQuery !== "string") {
|
|
313
650
|
return res.status(400).json({ error: "Query is required" });
|
|
314
651
|
}
|
|
315
|
-
const trimmedQuery = sqlQuery.trim().toUpperCase();
|
|
316
|
-
if (!trimmedQuery.startsWith("SELECT")) {
|
|
317
|
-
return res.status(400).json({
|
|
318
|
-
error: "Only SELECT queries are allowed (read-only mode)"
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
const dangerousKeywords = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE", "TRUNCATE", "EXEC", "EXECUTE"];
|
|
322
|
-
const hasDangerousKeyword = dangerousKeywords.some((keyword) => {
|
|
323
|
-
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
324
|
-
return regex.test(sqlQuery);
|
|
325
|
-
});
|
|
326
|
-
if (hasDangerousKeyword) {
|
|
327
|
-
return res.status(400).json({
|
|
328
|
-
error: "Query contains prohibited keywords. Only SELECT queries are allowed."
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
652
|
const startTime = Date.now();
|
|
332
653
|
const resultSets = await executeQueryMultiple(sqlQuery);
|
|
333
654
|
const executionTime = Date.now() - startTime;
|
|
@@ -339,7 +660,7 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
339
660
|
} catch (error) {
|
|
340
661
|
const errorMessage = error.message || "";
|
|
341
662
|
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
342
|
-
const { disconnect: disconnect2 } = await import("./mssql-
|
|
663
|
+
const { disconnect: disconnect2 } = await import("./mssql-HI3S4B7E.js");
|
|
343
664
|
await disconnect2();
|
|
344
665
|
}
|
|
345
666
|
res.status(500).json({
|