datapeek 0.1.10 → 0.1.12
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-2I4MNNVK.js +628 -0
- package/dist/cli/chunk-4GDQ7VJZ.js +662 -0
- package/dist/cli/chunk-4MIQHH6K.js +47 -0
- package/dist/cli/chunk-6K3WSEIM.js +375 -0
- package/dist/cli/chunk-6PMCDR7J.js +628 -0
- package/dist/cli/chunk-B7K46745.js +605 -0
- package/dist/cli/chunk-CK2O76SG.js +713 -0
- package/dist/cli/chunk-FXOMCTFV.js +696 -0
- package/dist/cli/chunk-G4ID5SFT.js +628 -0
- package/dist/cli/chunk-GCVFEHSZ.js +708 -0
- package/dist/cli/chunk-LGE2JQ3S.js +628 -0
- package/dist/cli/chunk-NDLKLB5A.js +710 -0
- package/dist/cli/chunk-PG6I26XT.js +614 -0
- package/dist/cli/chunk-RK2Q2AX5.js +582 -0
- package/dist/cli/chunk-S5LRCCK6.js +582 -0
- package/dist/cli/chunk-UBFQXPPF.js +701 -0
- package/dist/cli/chunk-X2DQBOP6.js +712 -0
- package/dist/cli/chunk-XRMPVZIS.js +709 -0
- package/dist/cli/chunk-Z5G3UEXN.js +711 -0
- package/dist/cli/chunk-ZMP7S3G6.js +696 -0
- package/dist/cli/db-32TDDTFP.js +28 -0
- package/dist/cli/db-47O74DN5.js +34 -0
- package/dist/cli/db-4U22OWGB.js +29 -0
- package/dist/cli/db-CL72XDNO.js +34 -0
- package/dist/cli/db-CZCZOEID.js +28 -0
- package/dist/cli/db-IX3DXZ2C.js +34 -0
- package/dist/cli/db-LJCRTZTY.js +34 -0
- package/dist/cli/db-LNX4UMRF.js +28 -0
- package/dist/cli/db-NESFXDWL.js +28 -0
- package/dist/cli/db-OIJ2PJK6.js +34 -0
- package/dist/cli/db-OPAF6ZO3.js +28 -0
- package/dist/cli/db-PQURQHSK.js +28 -0
- package/dist/cli/db-QN5MTDYB.js +28 -0
- package/dist/cli/db-RBMUR3OK.js +34 -0
- package/dist/cli/db-REMUYLPP.js +34 -0
- package/dist/cli/db-RTHQ4ZAU.js +28 -0
- package/dist/cli/db-SHFLGGWD.js +28 -0
- package/dist/cli/db-ULMGZXQ5.js +34 -0
- package/dist/cli/db-YOGJ4LXE.js +28 -0
- package/dist/cli/dev.js +717 -291
- package/dist/cli/index.js +720 -293
- package/dist/cli/queryCancellation-4CMKYTFU.js +46 -0
- package/dist/cli/queryCancellation-E5O4FBUY.js +14 -0
- package/dist/client/assets/index-BXssAVuE.css +1 -0
- package/dist/client/assets/index-DgwSpfvt.js +378 -0
- package/dist/client/index.html +2 -2
- package/dist/server/chunk-26GSUAI4.js +710 -0
- package/dist/server/chunk-36OOXHKI.js +659 -0
- package/dist/server/chunk-3DAIYH42.js +44 -0
- package/dist/server/chunk-4ELMZYWA.js +611 -0
- package/dist/server/chunk-5ESS5YBD.js +707 -0
- package/dist/server/chunk-7HIBWXBL.js +625 -0
- package/dist/server/chunk-BH22Q2FV.js +705 -0
- package/dist/server/chunk-CFU2CLKS.js +693 -0
- package/dist/server/chunk-CXA3HXTK.js +698 -0
- package/dist/server/chunk-DAUUXYQF.js +706 -0
- package/dist/server/chunk-FMSIEQ2T.js +579 -0
- package/dist/server/chunk-G6A463RM.js +625 -0
- package/dist/server/chunk-H6DUOXOK.js +625 -0
- package/dist/server/chunk-JCRZ6H7P.js +579 -0
- package/dist/server/chunk-L6LL3NE7.js +602 -0
- package/dist/server/chunk-TH6TUILG.js +709 -0
- package/dist/server/chunk-UJ2LRK25.js +373 -0
- package/dist/server/chunk-YI67PW64.js +625 -0
- package/dist/server/chunk-ZDZ2C5ZQ.js +693 -0
- package/dist/server/chunk-ZJOVVCPT.js +708 -0
- package/dist/server/db-22DQ6YG7.js +32 -0
- package/dist/server/db-3CBO6RHU.js +26 -0
- package/dist/server/db-3XN2KWYT.js +32 -0
- package/dist/server/db-3Y6K4EDP.js +26 -0
- package/dist/server/db-57HXNWPD.js +26 -0
- package/dist/server/db-5GCTDKYH.js +27 -0
- package/dist/server/db-5VQOTJ52.js +26 -0
- package/dist/server/db-B5L7VQ7E.js +32 -0
- package/dist/server/db-CJPCGHL3.js +32 -0
- package/dist/server/db-DAQ4BVCU.js +32 -0
- package/dist/server/db-EEA6EWHL.js +26 -0
- package/dist/server/db-KR7Z5M5V.js +32 -0
- package/dist/server/db-LMSBISDM.js +26 -0
- package/dist/server/db-LSOQE7N7.js +32 -0
- package/dist/server/db-NHBP6YLH.js +26 -0
- package/dist/server/db-OWVJVBBD.js +26 -0
- package/dist/server/db-TTVXKX7P.js +26 -0
- package/dist/server/db-YJ753EQV.js +26 -0
- package/dist/server/db-ZV6GZHFU.js +32 -0
- package/dist/server/dev.js +717 -291
- package/dist/server/index.js +717 -291
- package/dist/server/queryCancellation-M56DISJX.js +43 -0
- package/dist/server/queryCancellation-T2HSNMIV.js +12 -0
- package/package.json +3 -1
- package/dist/client/assets/index-BhZ7NrhL.css +0 -1
- package/dist/client/assets/index-D-mash8_.js +0 -370
package/dist/cli/dev.js
CHANGED
|
@@ -6,8 +6,15 @@ import {
|
|
|
6
6
|
executeQuery,
|
|
7
7
|
executeQueryMultiple,
|
|
8
8
|
getConnection,
|
|
9
|
+
getDbType,
|
|
10
|
+
getDialect,
|
|
11
|
+
setDbType,
|
|
9
12
|
testConnection
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CK2O76SG.js";
|
|
14
|
+
import {
|
|
15
|
+
cancelQuery,
|
|
16
|
+
generateQueryId
|
|
17
|
+
} from "./chunk-4MIQHH6K.js";
|
|
11
18
|
|
|
12
19
|
// src/server/index.ts
|
|
13
20
|
import express from "express";
|
|
@@ -27,9 +34,12 @@ connectionRoutes.post("/test", async (req, res) => {
|
|
|
27
34
|
message: "Connection test timeout"
|
|
28
35
|
});
|
|
29
36
|
}
|
|
30
|
-
},
|
|
37
|
+
}, 18e5);
|
|
31
38
|
try {
|
|
32
39
|
const config = req.body;
|
|
40
|
+
if (config.dbType) {
|
|
41
|
+
setDbType(config.dbType);
|
|
42
|
+
}
|
|
33
43
|
await testConnection(config);
|
|
34
44
|
clearTimeout(timeout);
|
|
35
45
|
if (!res.headersSent) {
|
|
@@ -57,9 +67,12 @@ connectionRoutes.post("/", async (req, res) => {
|
|
|
57
67
|
message: "Connection timeout"
|
|
58
68
|
});
|
|
59
69
|
}
|
|
60
|
-
},
|
|
70
|
+
}, 18e5);
|
|
61
71
|
try {
|
|
62
72
|
const config = req.body;
|
|
73
|
+
if (config.dbType) {
|
|
74
|
+
setDbType(config.dbType);
|
|
75
|
+
}
|
|
63
76
|
await connect(config);
|
|
64
77
|
clearTimeout(timeout);
|
|
65
78
|
if (!res.headersSent) {
|
|
@@ -88,19 +101,28 @@ connectionRoutes.delete("/", async (req, res) => {
|
|
|
88
101
|
});
|
|
89
102
|
connectionRoutes.get("/status", async (req, res) => {
|
|
90
103
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
await
|
|
104
|
+
const pool = getConnection();
|
|
105
|
+
if (pool) {
|
|
106
|
+
let isConnected = false;
|
|
107
|
+
if ("connected" in pool) {
|
|
108
|
+
isConnected = pool.connected === true;
|
|
109
|
+
} else {
|
|
110
|
+
isConnected = !pool.ended;
|
|
111
|
+
}
|
|
112
|
+
if (isConnected) {
|
|
113
|
+
try {
|
|
114
|
+
const dialect = getDialect();
|
|
115
|
+
const result = await executeQuery(dialect.currentDbQuery());
|
|
116
|
+
const databaseName = result[0]?.databaseName || null;
|
|
117
|
+
res.json({ connected: true, databaseName });
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const errorMessage = error.message || "";
|
|
120
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
121
|
+
await disconnect();
|
|
122
|
+
}
|
|
123
|
+
res.json({ connected: false });
|
|
103
124
|
}
|
|
125
|
+
} else {
|
|
104
126
|
res.json({ connected: false });
|
|
105
127
|
}
|
|
106
128
|
} else {
|
|
@@ -113,28 +135,34 @@ connectionRoutes.get("/status", async (req, res) => {
|
|
|
113
135
|
|
|
114
136
|
// src/server/routes/tables.ts
|
|
115
137
|
import { Router as Router2 } from "express";
|
|
116
|
-
import sql from "mssql";
|
|
117
138
|
var tableRoutes = Router2();
|
|
118
139
|
tableRoutes.get("/", async (req, res) => {
|
|
119
140
|
try {
|
|
120
141
|
const pool = getConnection();
|
|
121
|
-
if (!pool
|
|
142
|
+
if (!pool) {
|
|
143
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
144
|
+
}
|
|
145
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
146
|
+
if (!isConnected) {
|
|
122
147
|
return res.status(400).json({ error: "Not connected to database" });
|
|
123
148
|
}
|
|
149
|
+
const dialect = getDialect();
|
|
150
|
+
const dbType = getDbType();
|
|
151
|
+
const schemaFilter = dbType === "postgres" ? `AND table_schema NOT IN ('pg_catalog', 'information_schema')` : "";
|
|
124
152
|
const query = `
|
|
125
153
|
SELECT
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
FROM
|
|
129
|
-
WHERE
|
|
130
|
-
ORDER BY
|
|
154
|
+
table_schema as "schemaName",
|
|
155
|
+
table_name as "tableName"
|
|
156
|
+
FROM information_schema.tables
|
|
157
|
+
WHERE table_type = 'BASE TABLE'${schemaFilter}
|
|
158
|
+
ORDER BY table_schema, table_name
|
|
131
159
|
`;
|
|
132
160
|
const result = await executeQuery(query);
|
|
133
161
|
res.json(result);
|
|
134
162
|
} catch (error) {
|
|
135
163
|
const errorMessage = error.message || "";
|
|
136
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
137
|
-
const { disconnect: disconnect2 } = await import("./
|
|
164
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
165
|
+
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
138
166
|
await disconnect2();
|
|
139
167
|
}
|
|
140
168
|
res.status(500).json({ error: error.message || "Failed to fetch tables" });
|
|
@@ -144,64 +172,69 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
|
144
172
|
try {
|
|
145
173
|
const { schema, table } = req.params;
|
|
146
174
|
const pool = getConnection();
|
|
147
|
-
if (!pool
|
|
175
|
+
if (!pool) {
|
|
176
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
177
|
+
}
|
|
178
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
179
|
+
if (!isConnected) {
|
|
148
180
|
return res.status(400).json({ error: "Not connected to database" });
|
|
149
181
|
}
|
|
182
|
+
const dialect = getDialect();
|
|
150
183
|
const query = `
|
|
151
184
|
SELECT
|
|
152
|
-
c.
|
|
153
|
-
c.
|
|
154
|
-
c.
|
|
155
|
-
c.
|
|
156
|
-
c.
|
|
157
|
-
CASE WHEN pk.
|
|
158
|
-
fk.
|
|
159
|
-
fk.
|
|
160
|
-
fk.
|
|
161
|
-
FROM
|
|
185
|
+
c.column_name as "columnName",
|
|
186
|
+
c.data_type as "dataType",
|
|
187
|
+
c.character_maximum_length as "maxLength",
|
|
188
|
+
c.is_nullable as "isNullable",
|
|
189
|
+
c.column_default as "defaultValue",
|
|
190
|
+
CASE WHEN pk.column_name IS NOT NULL THEN 1 ELSE 0 END as "isPrimaryKey",
|
|
191
|
+
fk.referenced_table_schema as "referencedSchema",
|
|
192
|
+
fk.referenced_table_name as "referencedTable",
|
|
193
|
+
fk.referenced_column_name as "referencedColumn"
|
|
194
|
+
FROM information_schema.columns c
|
|
162
195
|
LEFT JOIN (
|
|
163
|
-
SELECT ku.
|
|
164
|
-
FROM
|
|
165
|
-
INNER JOIN
|
|
166
|
-
ON tc.
|
|
167
|
-
AND tc.
|
|
168
|
-
) pk ON c.
|
|
169
|
-
AND c.
|
|
170
|
-
AND c.
|
|
196
|
+
SELECT ku.table_schema, ku.table_name, ku.column_name
|
|
197
|
+
FROM information_schema.table_constraints tc
|
|
198
|
+
INNER JOIN information_schema.key_column_usage ku
|
|
199
|
+
ON tc.constraint_type = 'PRIMARY KEY'
|
|
200
|
+
AND tc.constraint_name = ku.constraint_name
|
|
201
|
+
) pk ON c.table_schema = pk.table_schema
|
|
202
|
+
AND c.table_name = pk.table_name
|
|
203
|
+
AND c.column_name = pk.column_name
|
|
171
204
|
LEFT JOIN (
|
|
172
205
|
SELECT
|
|
173
|
-
kcu1.
|
|
174
|
-
kcu1.
|
|
175
|
-
kcu1.
|
|
176
|
-
kcu2.
|
|
177
|
-
kcu2.
|
|
178
|
-
kcu2.
|
|
179
|
-
FROM
|
|
180
|
-
INNER JOIN
|
|
181
|
-
ON rc.
|
|
182
|
-
AND rc.
|
|
183
|
-
AND rc.
|
|
184
|
-
INNER JOIN
|
|
185
|
-
ON rc.
|
|
186
|
-
AND rc.
|
|
187
|
-
AND rc.
|
|
188
|
-
AND kcu1.
|
|
189
|
-
) fk ON c.
|
|
190
|
-
AND c.
|
|
191
|
-
AND c.
|
|
192
|
-
WHERE c.
|
|
193
|
-
AND c.
|
|
194
|
-
ORDER BY c.
|
|
206
|
+
kcu1.table_schema,
|
|
207
|
+
kcu1.table_name,
|
|
208
|
+
kcu1.column_name,
|
|
209
|
+
kcu2.table_schema as referenced_table_schema,
|
|
210
|
+
kcu2.table_name as referenced_table_name,
|
|
211
|
+
kcu2.column_name as referenced_column_name
|
|
212
|
+
FROM information_schema.referential_constraints rc
|
|
213
|
+
INNER JOIN information_schema.key_column_usage kcu1
|
|
214
|
+
ON rc.constraint_catalog = kcu1.constraint_catalog
|
|
215
|
+
AND rc.constraint_schema = kcu1.constraint_schema
|
|
216
|
+
AND rc.constraint_name = kcu1.constraint_name
|
|
217
|
+
INNER JOIN information_schema.key_column_usage kcu2
|
|
218
|
+
ON rc.unique_constraint_catalog = kcu2.constraint_catalog
|
|
219
|
+
AND rc.unique_constraint_schema = kcu2.constraint_schema
|
|
220
|
+
AND rc.unique_constraint_name = kcu2.constraint_name
|
|
221
|
+
AND kcu1.ordinal_position = kcu2.ordinal_position
|
|
222
|
+
) fk ON c.table_schema = fk.table_schema
|
|
223
|
+
AND c.table_name = fk.table_name
|
|
224
|
+
AND c.column_name = fk.column_name
|
|
225
|
+
WHERE c.table_schema = @schema
|
|
226
|
+
AND c.table_name = @table
|
|
227
|
+
ORDER BY c.ordinal_position
|
|
195
228
|
`;
|
|
196
229
|
const result = await executeQuery(query, [
|
|
197
|
-
{ name: "schema", value: schema
|
|
198
|
-
{ name: "table", value: table
|
|
230
|
+
{ name: "schema", value: schema },
|
|
231
|
+
{ name: "table", value: table }
|
|
199
232
|
]);
|
|
200
233
|
res.json(result);
|
|
201
234
|
} catch (error) {
|
|
202
235
|
const errorMessage = error.message || "";
|
|
203
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
204
|
-
const { disconnect: disconnect2 } = await import("./
|
|
236
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
237
|
+
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
205
238
|
await disconnect2();
|
|
206
239
|
}
|
|
207
240
|
res.status(500).json({ error: error.message || "Failed to fetch table structure" });
|
|
@@ -216,62 +249,230 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
216
249
|
const sortDirection = req.query.sortDirection || "asc";
|
|
217
250
|
const fkDisplayMode = req.query.fkDisplayMode || "key-only";
|
|
218
251
|
const offset = (page - 1) * pageSize;
|
|
219
|
-
const
|
|
252
|
+
const parsedFilters = [];
|
|
253
|
+
const seenFilterColumns = /* @__PURE__ */ new Set();
|
|
254
|
+
const parseFilterEntry = (columnName, rawValue) => {
|
|
255
|
+
if (!columnName || rawValue === null || rawValue === void 0 || seenFilterColumns.has(columnName)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const filterValue = String(rawValue).trim();
|
|
259
|
+
if (!filterValue) return;
|
|
260
|
+
try {
|
|
261
|
+
const parsed = JSON.parse(filterValue);
|
|
262
|
+
if (parsed && typeof parsed === "object" && parsed.operator) {
|
|
263
|
+
parsedFilters.push({
|
|
264
|
+
column: columnName,
|
|
265
|
+
operator: parsed.operator,
|
|
266
|
+
value: parsed.value,
|
|
267
|
+
dataType: parsed.dataType
|
|
268
|
+
});
|
|
269
|
+
} else {
|
|
270
|
+
parsedFilters.push({
|
|
271
|
+
column: columnName,
|
|
272
|
+
operator: "contains",
|
|
273
|
+
value: filterValue
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
} catch {
|
|
277
|
+
parsedFilters.push({
|
|
278
|
+
column: columnName,
|
|
279
|
+
operator: "contains",
|
|
280
|
+
value: filterValue
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
seenFilterColumns.add(columnName);
|
|
284
|
+
};
|
|
220
285
|
Object.keys(req.query).forEach((key) => {
|
|
221
286
|
const match = key.match(/^filter\[(.+)\]$/);
|
|
222
|
-
if (match
|
|
223
|
-
|
|
287
|
+
if (match) {
|
|
288
|
+
const columnName = match[1];
|
|
289
|
+
parseFilterEntry(columnName, req.query[key]);
|
|
224
290
|
}
|
|
225
291
|
});
|
|
226
|
-
|
|
227
|
-
|
|
292
|
+
const nestedFilters = req.query.filter;
|
|
293
|
+
if (nestedFilters && typeof nestedFilters === "object" && !Array.isArray(nestedFilters)) {
|
|
294
|
+
Object.entries(nestedFilters).forEach(([columnName, rawValue]) => {
|
|
295
|
+
parseFilterEntry(columnName, rawValue);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
console.log("Parsed filters:", parsedFilters);
|
|
228
299
|
const pool = getConnection();
|
|
229
|
-
if (!pool
|
|
300
|
+
if (!pool) {
|
|
301
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
302
|
+
}
|
|
303
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
304
|
+
if (!isConnected) {
|
|
230
305
|
return res.status(400).json({ error: "Not connected to database" });
|
|
231
306
|
}
|
|
232
|
-
const
|
|
233
|
-
|
|
307
|
+
const dialect = getDialect();
|
|
308
|
+
const columnMetadata = {};
|
|
309
|
+
if (parsedFilters.length > 0) {
|
|
234
310
|
try {
|
|
235
|
-
const columnNames =
|
|
236
|
-
const placeholders = columnNames.map((_, i) =>
|
|
311
|
+
const columnNames = [...new Set(parsedFilters.map((f) => f.column))];
|
|
312
|
+
const placeholders = columnNames.map((_, i) => dialect.param(i + 3)).join(", ");
|
|
237
313
|
const validateQuery = `
|
|
238
|
-
SELECT
|
|
239
|
-
FROM
|
|
240
|
-
WHERE
|
|
314
|
+
SELECT column_name, data_type
|
|
315
|
+
FROM information_schema.columns
|
|
316
|
+
WHERE table_schema = ${dialect.param(1)} AND table_name = ${dialect.param(2)} AND column_name IN (${placeholders})
|
|
241
317
|
`;
|
|
242
318
|
const validateParams = [
|
|
243
|
-
{ name: "schema", value: schema
|
|
244
|
-
{ name: "table", value: table
|
|
245
|
-
...columnNames.map((col
|
|
319
|
+
{ name: "schema", value: schema },
|
|
320
|
+
{ name: "table", value: table },
|
|
321
|
+
...columnNames.map((col) => ({ name: "col", value: col }))
|
|
246
322
|
];
|
|
247
323
|
const validateResult = await executeQuery(validateQuery, validateParams);
|
|
248
|
-
|
|
249
|
-
|
|
324
|
+
validateResult.forEach((r) => {
|
|
325
|
+
const colName = r.column_name || r.COLUMN_NAME;
|
|
326
|
+
const dataType = r.data_type || r.DATA_TYPE;
|
|
327
|
+
columnMetadata[colName] = {
|
|
328
|
+
dataType,
|
|
329
|
+
exists: true
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
console.log("Column metadata:", columnMetadata);
|
|
250
333
|
} catch (e) {
|
|
251
334
|
console.error("Error validating filter columns:", e);
|
|
252
|
-
console.error("Filters that failed validation:", filters);
|
|
253
335
|
}
|
|
254
336
|
}
|
|
255
337
|
let whereClause = "";
|
|
256
338
|
const filterParams = [];
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
339
|
+
let paramIndex = 1;
|
|
340
|
+
if (parsedFilters.length > 0) {
|
|
341
|
+
const whereConditions = [];
|
|
342
|
+
parsedFilters.forEach((filter) => {
|
|
343
|
+
const columnName = filter.column;
|
|
344
|
+
const metadata = columnMetadata[columnName];
|
|
345
|
+
if (!metadata || !metadata.exists) {
|
|
346
|
+
console.warn(`Filter column ${columnName} not found, skipping`);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const dataType = filter.dataType || metadata.dataType;
|
|
350
|
+
const operator = filter.operator;
|
|
351
|
+
const value = filter.value;
|
|
352
|
+
if (value === null || value === void 0 || value === "") {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const quotedColumn = dialect.quoteId(columnName);
|
|
356
|
+
try {
|
|
357
|
+
let condition = "";
|
|
358
|
+
switch (operator) {
|
|
359
|
+
// Text operators
|
|
360
|
+
case "contains":
|
|
361
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}%` });
|
|
362
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
363
|
+
break;
|
|
364
|
+
case "equals":
|
|
365
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
366
|
+
condition = `${quotedColumn} = ${dialect.param(paramIndex)}`;
|
|
367
|
+
break;
|
|
368
|
+
case "startsWith":
|
|
369
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `${String(value)}%` });
|
|
370
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
371
|
+
break;
|
|
372
|
+
case "endsWith":
|
|
373
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}` });
|
|
374
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
375
|
+
break;
|
|
376
|
+
case "notContains":
|
|
377
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}%` });
|
|
378
|
+
condition = `${quotedColumn} NOT LIKE ${dialect.param(paramIndex)}`;
|
|
379
|
+
break;
|
|
380
|
+
// Number operators
|
|
381
|
+
case "eq":
|
|
382
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
383
|
+
condition = `${quotedColumn} = ${dialect.param(paramIndex)}`;
|
|
384
|
+
break;
|
|
385
|
+
case "gt":
|
|
386
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
387
|
+
condition = `${quotedColumn} > ${dialect.param(paramIndex)}`;
|
|
388
|
+
break;
|
|
389
|
+
case "gte":
|
|
390
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
391
|
+
condition = `${quotedColumn} >= ${dialect.param(paramIndex)}`;
|
|
392
|
+
break;
|
|
393
|
+
case "lt":
|
|
394
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
395
|
+
condition = `${quotedColumn} < ${dialect.param(paramIndex)}`;
|
|
396
|
+
break;
|
|
397
|
+
case "lte":
|
|
398
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
399
|
+
condition = `${quotedColumn} <= ${dialect.param(paramIndex)}`;
|
|
400
|
+
break;
|
|
401
|
+
case "between":
|
|
402
|
+
if (typeof value === "object" && "from" in value && "to" in value) {
|
|
403
|
+
const fromParamIndex = paramIndex;
|
|
404
|
+
const toParamIndex = paramIndex + 1;
|
|
405
|
+
filterParams.push({ name: `filter${fromParamIndex}`, value: Number(value.from) });
|
|
406
|
+
filterParams.push({ name: `filter${toParamIndex}`, value: Number(value.to) });
|
|
407
|
+
condition = `${quotedColumn} BETWEEN ${dialect.param(fromParamIndex)} AND ${dialect.param(toParamIndex)}`;
|
|
408
|
+
paramIndex++;
|
|
409
|
+
}
|
|
410
|
+
break;
|
|
411
|
+
// Date operators
|
|
412
|
+
case "dateEq":
|
|
413
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
414
|
+
condition = `${dialect.castToDate(quotedColumn)} = ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
415
|
+
break;
|
|
416
|
+
case "dateAfter":
|
|
417
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
418
|
+
condition = `${dialect.castToDate(quotedColumn)} > ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
419
|
+
break;
|
|
420
|
+
case "dateBefore":
|
|
421
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
422
|
+
condition = `${dialect.castToDate(quotedColumn)} < ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
423
|
+
break;
|
|
424
|
+
case "dateBetween":
|
|
425
|
+
if (typeof value === "object" && "from" in value && "to" in value) {
|
|
426
|
+
const fromParamIndex = paramIndex;
|
|
427
|
+
const toParamIndex = paramIndex + 1;
|
|
428
|
+
filterParams.push({ name: `filter${fromParamIndex}`, value: String(value.from) });
|
|
429
|
+
filterParams.push({ name: `filter${toParamIndex}`, value: String(value.to) });
|
|
430
|
+
condition = `${dialect.castToDate(quotedColumn)} BETWEEN ${dialect.castToDate(dialect.param(fromParamIndex))} AND ${dialect.castToDate(dialect.param(toParamIndex))}`;
|
|
431
|
+
paramIndex++;
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
// Multiple select operators
|
|
435
|
+
case "in":
|
|
436
|
+
case "notIn":
|
|
437
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
438
|
+
const placeholders = [];
|
|
439
|
+
value.forEach((val, i) => {
|
|
440
|
+
const currentIndex = paramIndex + i;
|
|
441
|
+
filterParams.push({ name: `filter${currentIndex}`, value: val });
|
|
442
|
+
placeholders.push(dialect.param(currentIndex));
|
|
443
|
+
});
|
|
444
|
+
const inOperator = operator === "in" ? "IN" : "NOT IN";
|
|
445
|
+
condition = `${quotedColumn} ${inOperator} (${placeholders.join(", ")})`;
|
|
446
|
+
paramIndex += value.length - 1;
|
|
447
|
+
}
|
|
448
|
+
break;
|
|
449
|
+
default:
|
|
450
|
+
console.warn(`Unknown filter operator: ${operator}, falling back to contains`);
|
|
451
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}%` });
|
|
452
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
453
|
+
}
|
|
454
|
+
if (condition) {
|
|
455
|
+
whereConditions.push(condition);
|
|
456
|
+
paramIndex++;
|
|
457
|
+
}
|
|
458
|
+
} catch (error) {
|
|
459
|
+
console.error(`Error building filter condition for ${columnName}:`, error);
|
|
460
|
+
}
|
|
261
461
|
});
|
|
262
|
-
if (
|
|
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
|
-
});
|
|
462
|
+
if (whereConditions.length > 0) {
|
|
268
463
|
whereClause = `WHERE ${whereConditions.join(" AND ")}`;
|
|
269
464
|
console.log("Applying WHERE clause:", whereClause);
|
|
270
|
-
console.log("Filter params:", filterParams);
|
|
271
|
-
console.log("Valid filters:", validFilters);
|
|
465
|
+
console.log("Filter params:", filterParams.map((p) => ({ name: p.name, value: p.value })));
|
|
272
466
|
}
|
|
273
467
|
}
|
|
274
|
-
const
|
|
468
|
+
const qualifiedDataWhereClause = parsedFilters.reduce((clause, filter) => {
|
|
469
|
+
const quotedColumn = dialect.quoteId(filter.column);
|
|
470
|
+
const quotedTableColumn = `${dialect.quoteId("t")}.${quotedColumn}`;
|
|
471
|
+
return clause.replace(new RegExp(quotedColumn.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), quotedTableColumn);
|
|
472
|
+
}, whereClause);
|
|
473
|
+
const quotedSchema = dialect.quoteId(schema);
|
|
474
|
+
const quotedTable = dialect.quoteId(table);
|
|
475
|
+
const countQuery = `SELECT COUNT(*) as total FROM ${quotedSchema}.${quotedTable}${whereClause ? " " + whereClause : ""}`;
|
|
275
476
|
const countResult = await executeQuery(countQuery, filterParams.length > 0 ? filterParams : []);
|
|
276
477
|
const total = countResult[0]?.total || 0;
|
|
277
478
|
let orderByColumn = sortColumn || "";
|
|
@@ -279,17 +480,19 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
279
480
|
if (orderByColumn) {
|
|
280
481
|
try {
|
|
281
482
|
const validateQuery = `
|
|
282
|
-
SELECT
|
|
283
|
-
FROM
|
|
284
|
-
WHERE
|
|
483
|
+
SELECT column_name
|
|
484
|
+
FROM information_schema.columns
|
|
485
|
+
WHERE table_schema = ${dialect.param(1)} AND table_name = ${dialect.param(2)} AND column_name = ${dialect.param(3)}
|
|
285
486
|
`;
|
|
286
487
|
const validateResult = await executeQuery(validateQuery, [
|
|
287
|
-
{ name: "schema", value: schema
|
|
288
|
-
{ name: "table", value: table
|
|
289
|
-
{ name: "column", value: orderByColumn
|
|
488
|
+
{ name: "schema", value: schema },
|
|
489
|
+
{ name: "table", value: table },
|
|
490
|
+
{ name: "column", value: orderByColumn }
|
|
290
491
|
]);
|
|
291
492
|
if (validateResult.length === 0) {
|
|
292
493
|
orderByColumn = "";
|
|
494
|
+
} else {
|
|
495
|
+
orderByColumn = validateResult[0].column_name || validateResult[0].COLUMN_NAME || orderByColumn;
|
|
293
496
|
}
|
|
294
497
|
} catch (e) {
|
|
295
498
|
orderByColumn = "";
|
|
@@ -297,18 +500,20 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
297
500
|
}
|
|
298
501
|
if (!orderByColumn) {
|
|
299
502
|
try {
|
|
503
|
+
const topClause = dialect.topN(1);
|
|
300
504
|
const structureQuery = `
|
|
301
|
-
SELECT
|
|
302
|
-
FROM
|
|
303
|
-
WHERE
|
|
304
|
-
ORDER BY
|
|
505
|
+
SELECT ${topClause ? topClause + " " : ""}column_name
|
|
506
|
+
FROM information_schema.columns
|
|
507
|
+
WHERE table_schema = ${dialect.param(1)} AND table_name = ${dialect.param(2)}
|
|
508
|
+
ORDER BY ordinal_position
|
|
509
|
+
${!topClause ? dialect.limitOffset(0, 1) : ""}
|
|
305
510
|
`;
|
|
306
511
|
const structureResult = await executeQuery(structureQuery, [
|
|
307
|
-
{ name: "schema", value: schema
|
|
308
|
-
{ name: "table", value: table
|
|
512
|
+
{ name: "schema", value: schema },
|
|
513
|
+
{ name: "table", value: table }
|
|
309
514
|
]);
|
|
310
515
|
if (structureResult.length > 0) {
|
|
311
|
-
orderByColumn = structureResult[0].COLUMN_NAME;
|
|
516
|
+
orderByColumn = structureResult[0].column_name || structureResult[0].COLUMN_NAME;
|
|
312
517
|
}
|
|
313
518
|
} catch (e) {
|
|
314
519
|
}
|
|
@@ -316,93 +521,97 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
316
521
|
const fkJoins = [];
|
|
317
522
|
const fkSelects = [];
|
|
318
523
|
const fkDisplayColumns = {};
|
|
319
|
-
|
|
320
|
-
const fkQuery = `
|
|
524
|
+
const fkQuery = `
|
|
321
525
|
SELECT
|
|
322
|
-
kcu1.
|
|
323
|
-
kcu2.
|
|
324
|
-
kcu2.
|
|
325
|
-
kcu2.
|
|
326
|
-
FROM
|
|
327
|
-
INNER JOIN
|
|
328
|
-
ON rc.
|
|
329
|
-
AND rc.
|
|
330
|
-
AND rc.
|
|
331
|
-
INNER JOIN
|
|
332
|
-
ON rc.
|
|
333
|
-
AND rc.
|
|
334
|
-
AND rc.
|
|
335
|
-
AND kcu1.
|
|
336
|
-
WHERE kcu1.
|
|
337
|
-
AND kcu1.
|
|
526
|
+
kcu1.column_name as "fkColumnName",
|
|
527
|
+
kcu2.table_schema as "referencedSchema",
|
|
528
|
+
kcu2.table_name as "referencedTable",
|
|
529
|
+
kcu2.column_name as "referencedColumn"
|
|
530
|
+
FROM information_schema.referential_constraints rc
|
|
531
|
+
INNER JOIN information_schema.key_column_usage kcu1
|
|
532
|
+
ON rc.constraint_catalog = kcu1.constraint_catalog
|
|
533
|
+
AND rc.constraint_schema = kcu1.constraint_schema
|
|
534
|
+
AND rc.constraint_name = kcu1.constraint_name
|
|
535
|
+
INNER JOIN information_schema.key_column_usage kcu2
|
|
536
|
+
ON rc.unique_constraint_catalog = kcu2.constraint_catalog
|
|
537
|
+
AND rc.unique_constraint_schema = kcu2.constraint_schema
|
|
538
|
+
AND rc.unique_constraint_name = kcu2.constraint_name
|
|
539
|
+
AND kcu1.ordinal_position = kcu2.ordinal_position
|
|
540
|
+
WHERE kcu1.table_schema = ${dialect.param(1)}
|
|
541
|
+
AND kcu1.table_name = ${dialect.param(2)}
|
|
338
542
|
`;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
SELECT
|
|
355
|
-
FROM
|
|
543
|
+
console.log("Fetching foreign keys with query:", fkQuery);
|
|
544
|
+
const foreignKeys = await executeQuery(fkQuery, [
|
|
545
|
+
{ name: "schema", value: schema },
|
|
546
|
+
{ name: "table", value: table }
|
|
547
|
+
]);
|
|
548
|
+
console.log(`Found ${foreignKeys.length} foreign key(s)`);
|
|
549
|
+
if (foreignKeys.length > 0) {
|
|
550
|
+
const uniqueRefTables = Array.from(
|
|
551
|
+
new Set(foreignKeys.map((fk) => `${fk.referencedSchema || fk.referenced_schema}.${fk.referencedTable || fk.referenced_table}`))
|
|
552
|
+
);
|
|
553
|
+
const tableConditions = uniqueRefTables.map((tableRef, idx) => {
|
|
554
|
+
const [refSchema, refTable] = tableRef.split(".");
|
|
555
|
+
return `(table_schema = ${dialect.param(idx * 2 + 1)} AND table_name = ${dialect.param(idx * 2 + 2)})`;
|
|
556
|
+
}).join(" OR ");
|
|
557
|
+
const batchColumnsQuery = `
|
|
558
|
+
SELECT table_schema, table_name, column_name, data_type, ordinal_position
|
|
559
|
+
FROM information_schema.columns
|
|
356
560
|
WHERE ${tableConditions}
|
|
357
|
-
ORDER BY
|
|
561
|
+
ORDER BY table_schema, table_name, ordinal_position
|
|
358
562
|
`;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
columnsByTable[key]
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
563
|
+
const batchParams = uniqueRefTables.flatMap((tableRef) => {
|
|
564
|
+
const [refSchema, refTable] = tableRef.split(".");
|
|
565
|
+
return [
|
|
566
|
+
{ name: "refSchema", value: refSchema },
|
|
567
|
+
{ name: "refTable", value: refTable }
|
|
568
|
+
];
|
|
569
|
+
});
|
|
570
|
+
console.log("Fetching referenced table columns with query:", batchColumnsQuery);
|
|
571
|
+
console.log("Batch parameters:", batchParams.map((p) => ({ name: p.name, value: p.value })));
|
|
572
|
+
const allRefColumns = await executeQuery(batchColumnsQuery, batchParams);
|
|
573
|
+
console.log(`Found columns for ${uniqueRefTables.length} referenced table(s)`);
|
|
574
|
+
const columnsByTable = {};
|
|
575
|
+
allRefColumns.forEach((col) => {
|
|
576
|
+
const schemaName = col.table_schema || col.TABLE_SCHEMA;
|
|
577
|
+
const tableName = col.table_name || col.TABLE_NAME;
|
|
578
|
+
const key = `${schemaName}.${tableName}`;
|
|
579
|
+
if (!columnsByTable[key]) {
|
|
580
|
+
columnsByTable[key] = [];
|
|
581
|
+
}
|
|
582
|
+
columnsByTable[key].push(col);
|
|
583
|
+
});
|
|
584
|
+
for (const fk of foreignKeys) {
|
|
585
|
+
const fkColumn = fk.fkColumnName || fk.fk_column_name;
|
|
586
|
+
const refSchema = fk.referencedSchema || fk.referenced_schema;
|
|
587
|
+
const refTable = fk.referencedTable || fk.referenced_table;
|
|
588
|
+
const refColumn = fk.referencedColumn || fk.referenced_column;
|
|
589
|
+
const tableKey = `${refSchema}.${refTable}`;
|
|
590
|
+
const refColumns = columnsByTable[tableKey] || [];
|
|
591
|
+
const preferredNames = ["name", "title", "description", "code"];
|
|
592
|
+
let displayColumn = null;
|
|
593
|
+
for (const preferredName of preferredNames) {
|
|
594
|
+
const found = refColumns.find((col) => {
|
|
595
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
596
|
+
return colName.toLowerCase() === preferredName.toLowerCase();
|
|
597
|
+
});
|
|
598
|
+
if (found) {
|
|
599
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
600
|
+
break;
|
|
395
601
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
602
|
+
}
|
|
603
|
+
if (!displayColumn) {
|
|
604
|
+
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
605
|
+
const found = refColumns.find((col) => {
|
|
606
|
+
const dataType = col.data_type || col.DATA_TYPE;
|
|
607
|
+
return stringTypes.some((type) => dataType.toLowerCase().includes(type));
|
|
608
|
+
});
|
|
609
|
+
if (found) {
|
|
610
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
404
611
|
}
|
|
405
|
-
|
|
612
|
+
}
|
|
613
|
+
if (displayColumn) {
|
|
614
|
+
if (fkDisplayMode === "key-display" || fkDisplayMode === "display-only") {
|
|
406
615
|
const alias = `fk_${fkColumn}`;
|
|
407
616
|
fkJoins.push({
|
|
408
617
|
alias,
|
|
@@ -412,89 +621,79 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
412
621
|
refColumn,
|
|
413
622
|
displayColumn
|
|
414
623
|
});
|
|
415
|
-
fkSelects.push(`${alias}.
|
|
416
|
-
fkDisplayColumns[fkColumn] = displayColumn;
|
|
624
|
+
fkSelects.push(`${dialect.quoteId(alias)}.${dialect.quoteId(displayColumn)} as ${dialect.quoteId(`${fkColumn}_display`)}`);
|
|
417
625
|
}
|
|
626
|
+
fkDisplayColumns[fkColumn] = displayColumn;
|
|
418
627
|
}
|
|
419
628
|
}
|
|
420
629
|
}
|
|
421
630
|
const baseTableAlias = "t";
|
|
422
|
-
|
|
631
|
+
const quotedTableAlias = dialect.quoteId(baseTableAlias);
|
|
632
|
+
let baseSelects = `${quotedTableAlias}.*`;
|
|
423
633
|
if (fkDisplayMode === "display-only") {
|
|
424
634
|
const fkColumnNames = fkJoins.map((fk) => fk.fkColumn);
|
|
425
635
|
if (fkColumnNames.length > 0) {
|
|
426
|
-
baseSelects =
|
|
636
|
+
baseSelects = `${quotedTableAlias}.*`;
|
|
427
637
|
}
|
|
428
638
|
}
|
|
429
639
|
const allSelects = `${baseSelects}${fkSelects.length > 0 ? ", " + fkSelects.join(", ") : ""}`;
|
|
430
640
|
const buildJoinClauses = (tableAlias) => {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
641
|
+
const quotedAlias = dialect.quoteId(tableAlias);
|
|
642
|
+
return fkJoins.map((fk) => {
|
|
643
|
+
const quotedRefSchema = dialect.quoteId(fk.refSchema);
|
|
644
|
+
const quotedRefTable = dialect.quoteId(fk.refTable);
|
|
645
|
+
const quotedAliasName = dialect.quoteId(fk.alias);
|
|
646
|
+
const quotedFkColumn = dialect.quoteId(fk.fkColumn);
|
|
647
|
+
const quotedRefColumn = dialect.quoteId(fk.refColumn);
|
|
648
|
+
return `LEFT JOIN ${quotedRefSchema}.${quotedRefTable} ${quotedAliasName} ON ${quotedAlias}.${quotedFkColumn} = ${quotedAliasName}.${quotedRefColumn}`;
|
|
649
|
+
}).join("\n ");
|
|
434
650
|
};
|
|
435
651
|
let data;
|
|
436
652
|
let generatedQuery = "";
|
|
653
|
+
const offsetParamIndex = filterParams.length + 1;
|
|
654
|
+
const pageSizeParamIndex = filterParams.length + 2;
|
|
437
655
|
if (orderByColumn) {
|
|
656
|
+
const quotedOrderByColumn = dialect.quoteId(orderByColumn);
|
|
657
|
+
const limitOffsetClause = dialect.limitOffset(offset, pageSize);
|
|
438
658
|
const dataQuery = `
|
|
439
659
|
SELECT ${allSelects}
|
|
440
|
-
FROM
|
|
660
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}
|
|
441
661
|
${buildJoinClauses(baseTableAlias)}
|
|
442
|
-
${
|
|
443
|
-
ORDER BY ${
|
|
444
|
-
|
|
445
|
-
FETCH NEXT @pageSize ROWS ONLY
|
|
662
|
+
${qualifiedDataWhereClause}
|
|
663
|
+
ORDER BY ${quotedTableAlias}.${quotedOrderByColumn} ${orderByDirection}
|
|
664
|
+
${limitOffsetClause}
|
|
446
665
|
`;
|
|
447
666
|
generatedQuery = `SELECT ${allSelects}
|
|
448
|
-
FROM
|
|
449
|
-
ORDER BY ${
|
|
450
|
-
|
|
451
|
-
FETCH NEXT ${pageSize} ROWS ONLY`;
|
|
667
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}${fkJoins.length > 0 ? "\n" + buildJoinClauses(baseTableAlias) : ""}${qualifiedDataWhereClause ? "\n" + qualifiedDataWhereClause : ""}
|
|
668
|
+
ORDER BY ${quotedTableAlias}.${quotedOrderByColumn} ${orderByDirection}
|
|
669
|
+
${limitOffsetClause}`;
|
|
452
670
|
console.log("Executing SQL query:", dataQuery);
|
|
453
671
|
console.log("Query parameters:", {
|
|
454
672
|
offset,
|
|
455
673
|
pageSize,
|
|
456
674
|
filterParams: filterParams.map((p) => ({ name: p.name, value: p.value }))
|
|
457
675
|
});
|
|
458
|
-
data = await executeQuery(dataQuery,
|
|
459
|
-
{ name: "offset", value: offset, type: sql.Int },
|
|
460
|
-
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
461
|
-
...filterParams
|
|
462
|
-
]);
|
|
676
|
+
data = await executeQuery(dataQuery, filterParams);
|
|
463
677
|
} else {
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
FROM [${schema}].[${table}]
|
|
467
|
-
${whereClause}
|
|
468
|
-
`;
|
|
678
|
+
const dbType = getDbType();
|
|
679
|
+
const limitOffsetClause = dialect.limitOffset(offset, pageSize);
|
|
469
680
|
const dataQuery = `
|
|
470
681
|
SELECT ${allSelects}
|
|
471
|
-
FROM
|
|
682
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}
|
|
472
683
|
${buildJoinClauses(baseTableAlias)}
|
|
473
|
-
|
|
474
|
-
|
|
684
|
+
${qualifiedDataWhereClause}
|
|
685
|
+
${limitOffsetClause}
|
|
475
686
|
`;
|
|
476
687
|
generatedQuery = `SELECT ${allSelects}
|
|
477
|
-
FROM (
|
|
478
|
-
|
|
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`;
|
|
688
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}${fkJoins.length > 0 ? "\n" + buildJoinClauses(baseTableAlias) : ""}${qualifiedDataWhereClause ? "\n" + qualifiedDataWhereClause : ""}
|
|
689
|
+
${limitOffsetClause}`;
|
|
483
690
|
console.log("Executing SQL query:", dataQuery);
|
|
484
691
|
console.log("Query parameters:", {
|
|
485
692
|
offset,
|
|
486
693
|
pageSize,
|
|
487
694
|
filterParams: filterParams.map((p) => ({ name: p.name, value: p.value }))
|
|
488
695
|
});
|
|
489
|
-
data = await executeQuery(dataQuery,
|
|
490
|
-
{ name: "offset", value: offset, type: sql.Int },
|
|
491
|
-
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
492
|
-
...filterParams
|
|
493
|
-
]);
|
|
494
|
-
data = data.map((row) => {
|
|
495
|
-
const { rn, ...rest } = row;
|
|
496
|
-
return rest;
|
|
497
|
-
});
|
|
696
|
+
data = await executeQuery(dataQuery, filterParams);
|
|
498
697
|
}
|
|
499
698
|
if (fkDisplayMode === "display-only") {
|
|
500
699
|
const fkColumnNames = fkJoins.map((fk) => fk.fkColumn);
|
|
@@ -511,6 +710,34 @@ ORDER BY ${baseTableAlias}.rn`;
|
|
|
511
710
|
return filteredRow;
|
|
512
711
|
});
|
|
513
712
|
}
|
|
713
|
+
if (generatedQuery && filterParams.length > 0) {
|
|
714
|
+
const formatSqlLiteral = (value) => {
|
|
715
|
+
if (value === null || value === void 0) return "NULL";
|
|
716
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
|
|
717
|
+
if (typeof value === "boolean") return value ? "1" : "0";
|
|
718
|
+
if (value instanceof Date) return `'${value.toISOString().replace("T", " ").slice(0, 19)}'`;
|
|
719
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
720
|
+
};
|
|
721
|
+
const paramValues = new Map(
|
|
722
|
+
filterParams.map((p) => [p.name, p.value])
|
|
723
|
+
);
|
|
724
|
+
const dbType = getDbType();
|
|
725
|
+
if (dbType === "postgres") {
|
|
726
|
+
let paramIndex2 = 1;
|
|
727
|
+
generatedQuery = generatedQuery.replace(/\$(\d+)/g, (full, index) => {
|
|
728
|
+
const idx = parseInt(index, 10) - 1;
|
|
729
|
+
if (idx < filterParams.length) {
|
|
730
|
+
return formatSqlLiteral(filterParams[idx].value);
|
|
731
|
+
}
|
|
732
|
+
return full;
|
|
733
|
+
});
|
|
734
|
+
} else {
|
|
735
|
+
generatedQuery = generatedQuery.replace(/@([A-Za-z0-9_]+)/g, (full, name) => {
|
|
736
|
+
if (!paramValues.has(name)) return full;
|
|
737
|
+
return formatSqlLiteral(paramValues.get(name));
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
514
741
|
res.json({
|
|
515
742
|
data,
|
|
516
743
|
query: generatedQuery,
|
|
@@ -526,8 +753,8 @@ ORDER BY ${baseTableAlias}.rn`;
|
|
|
526
753
|
} catch (error) {
|
|
527
754
|
console.error("Error fetching table data:", error);
|
|
528
755
|
const errorMessage = error.message || "";
|
|
529
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
530
|
-
const { disconnect: disconnect2 } = await import("./
|
|
756
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
757
|
+
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
531
758
|
await disconnect2();
|
|
532
759
|
}
|
|
533
760
|
const isTimeout = error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT") || error.originalError?.code === "ETIMEOUT" || error.originalError?.code === "ESOCKET";
|
|
@@ -554,73 +781,68 @@ tableRoutes.post("/:schema/:table/related-data", async (req, res) => {
|
|
|
554
781
|
return res.status(400).json({ error: "Missing required parameters" });
|
|
555
782
|
}
|
|
556
783
|
const pool = getConnection();
|
|
557
|
-
if (!pool
|
|
784
|
+
if (!pool) {
|
|
785
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
786
|
+
}
|
|
787
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
788
|
+
if (!isConnected) {
|
|
558
789
|
return res.status(400).json({ error: "Not connected to database" });
|
|
559
790
|
}
|
|
791
|
+
const dialect = getDialect();
|
|
560
792
|
const columnsQuery = `
|
|
561
|
-
SELECT
|
|
562
|
-
FROM
|
|
563
|
-
WHERE
|
|
564
|
-
AND
|
|
565
|
-
ORDER BY
|
|
793
|
+
SELECT column_name, data_type, character_maximum_length
|
|
794
|
+
FROM information_schema.columns
|
|
795
|
+
WHERE table_schema = ${dialect.param(1)}
|
|
796
|
+
AND table_name = ${dialect.param(2)}
|
|
797
|
+
ORDER BY ordinal_position
|
|
566
798
|
`;
|
|
567
799
|
const columns = await executeQuery(columnsQuery, [
|
|
568
|
-
{ name: "refSchema", value: referencedSchema
|
|
569
|
-
{ name: "refTable", value: referencedTable
|
|
800
|
+
{ name: "refSchema", value: referencedSchema },
|
|
801
|
+
{ name: "refTable", value: referencedTable }
|
|
570
802
|
]);
|
|
571
|
-
const referencedColInfo = columns.find(
|
|
572
|
-
|
|
573
|
-
|
|
803
|
+
const referencedColInfo = columns.find((col) => {
|
|
804
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
805
|
+
return colName === referencedColumn;
|
|
806
|
+
});
|
|
574
807
|
if (!referencedColInfo) {
|
|
575
808
|
return res.status(400).json({ error: `Referenced column '${referencedColumn}' not found` });
|
|
576
809
|
}
|
|
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
810
|
const preferredNames = ["name", "title", "description", "code"];
|
|
594
811
|
let displayColumn = null;
|
|
595
812
|
for (const preferredName of preferredNames) {
|
|
596
|
-
const found = columns.find(
|
|
597
|
-
|
|
598
|
-
|
|
813
|
+
const found = columns.find((col) => {
|
|
814
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
815
|
+
return colName.toLowerCase() === preferredName.toLowerCase();
|
|
816
|
+
});
|
|
599
817
|
if (found) {
|
|
600
|
-
displayColumn = found.COLUMN_NAME;
|
|
818
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
601
819
|
break;
|
|
602
820
|
}
|
|
603
821
|
}
|
|
604
822
|
if (!displayColumn) {
|
|
605
823
|
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
606
|
-
const found = columns.find(
|
|
607
|
-
|
|
608
|
-
|
|
824
|
+
const found = columns.find((col) => {
|
|
825
|
+
const dataType = col.data_type || col.DATA_TYPE;
|
|
826
|
+
return stringTypes.some((type) => dataType.toLowerCase().includes(type));
|
|
827
|
+
});
|
|
609
828
|
if (found) {
|
|
610
|
-
displayColumn = found.COLUMN_NAME;
|
|
829
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
611
830
|
}
|
|
612
831
|
}
|
|
613
|
-
const
|
|
614
|
-
const
|
|
832
|
+
const quotedRefSchema = dialect.quoteId(referencedSchema);
|
|
833
|
+
const quotedRefTable = dialect.quoteId(referencedTable);
|
|
834
|
+
const quotedRefColumn = dialect.quoteId(referencedColumn);
|
|
835
|
+
const quotedDisplayColumn = displayColumn ? dialect.quoteId(displayColumn) : null;
|
|
836
|
+
const placeholders = ids.map((_, i) => dialect.param(i + 1)).join(", ");
|
|
837
|
+
const selectColumns = displayColumn ? `${quotedRefColumn}, ${quotedDisplayColumn}` : quotedRefColumn;
|
|
615
838
|
const dataQuery = `
|
|
616
839
|
SELECT ${selectColumns}
|
|
617
|
-
FROM
|
|
618
|
-
WHERE
|
|
840
|
+
FROM ${quotedRefSchema}.${quotedRefTable}
|
|
841
|
+
WHERE ${quotedRefColumn} IN (${placeholders})
|
|
619
842
|
`;
|
|
620
|
-
const params = ids.map((id
|
|
621
|
-
name:
|
|
622
|
-
value: id
|
|
623
|
-
type: referencedColumnType
|
|
843
|
+
const params = ids.map((id) => ({
|
|
844
|
+
name: "id",
|
|
845
|
+
value: id
|
|
624
846
|
}));
|
|
625
847
|
const result = await executeQuery(dataQuery, params);
|
|
626
848
|
const dataMap = {};
|
|
@@ -632,37 +854,223 @@ tableRoutes.post("/:schema/:table/related-data", async (req, res) => {
|
|
|
632
854
|
} catch (error) {
|
|
633
855
|
console.error("Error fetching related data:", error);
|
|
634
856
|
const errorMessage = error.message || "";
|
|
635
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
636
|
-
const { disconnect: disconnect2 } = await import("./
|
|
857
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
858
|
+
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
637
859
|
await disconnect2();
|
|
638
860
|
}
|
|
639
861
|
res.status(500).json({ error: error.message || "Failed to fetch related data" });
|
|
640
862
|
}
|
|
641
863
|
});
|
|
864
|
+
tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
865
|
+
try {
|
|
866
|
+
const { schema, table, column } = req.params;
|
|
867
|
+
const searchQuery = req.query.search;
|
|
868
|
+
const columnsParam = req.query.columns;
|
|
869
|
+
const pool = getConnection();
|
|
870
|
+
if (!pool) {
|
|
871
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
872
|
+
}
|
|
873
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
874
|
+
if (!isConnected) {
|
|
875
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
876
|
+
}
|
|
877
|
+
const dialect = getDialect();
|
|
878
|
+
const columnQuery = `
|
|
879
|
+
SELECT
|
|
880
|
+
c.column_name,
|
|
881
|
+
c.data_type,
|
|
882
|
+
fk.referenced_table_schema as "referencedSchema",
|
|
883
|
+
fk.referenced_table_name as "referencedTable",
|
|
884
|
+
fk.referenced_column_name as "referencedColumn"
|
|
885
|
+
FROM information_schema.columns c
|
|
886
|
+
LEFT JOIN (
|
|
887
|
+
SELECT
|
|
888
|
+
kcu1.table_schema,
|
|
889
|
+
kcu1.table_name,
|
|
890
|
+
kcu1.column_name,
|
|
891
|
+
kcu2.table_schema as referenced_table_schema,
|
|
892
|
+
kcu2.table_name as referenced_table_name,
|
|
893
|
+
kcu2.column_name as referenced_column_name
|
|
894
|
+
FROM information_schema.referential_constraints rc
|
|
895
|
+
INNER JOIN information_schema.key_column_usage kcu1
|
|
896
|
+
ON rc.constraint_catalog = kcu1.constraint_catalog
|
|
897
|
+
AND rc.constraint_schema = kcu1.constraint_schema
|
|
898
|
+
AND rc.constraint_name = kcu1.constraint_name
|
|
899
|
+
INNER JOIN information_schema.key_column_usage kcu2
|
|
900
|
+
ON rc.unique_constraint_catalog = kcu2.constraint_catalog
|
|
901
|
+
AND rc.unique_constraint_schema = kcu2.constraint_schema
|
|
902
|
+
AND rc.unique_constraint_name = kcu2.constraint_name
|
|
903
|
+
AND kcu1.ordinal_position = kcu2.ordinal_position
|
|
904
|
+
) fk ON c.table_schema = fk.table_schema
|
|
905
|
+
AND c.table_name = fk.table_name
|
|
906
|
+
AND c.column_name = fk.column_name
|
|
907
|
+
WHERE c.table_schema = ${dialect.param(1)} AND c.table_name = ${dialect.param(2)} AND c.column_name = ${dialect.param(3)}
|
|
908
|
+
`;
|
|
909
|
+
const columnResult = await executeQuery(columnQuery, [
|
|
910
|
+
{ name: "schema", value: schema },
|
|
911
|
+
{ name: "table", value: table },
|
|
912
|
+
{ name: "column", value: column }
|
|
913
|
+
]);
|
|
914
|
+
if (columnResult.length === 0) {
|
|
915
|
+
return res.status(400).json({ error: "Column not found" });
|
|
916
|
+
}
|
|
917
|
+
const columnInfo = columnResult[0];
|
|
918
|
+
const isForeignKey = !!(columnInfo.referencedSchema || columnInfo.referenced_schema) && !!(columnInfo.referencedTable || columnInfo.referenced_table);
|
|
919
|
+
let query = "";
|
|
920
|
+
const params = [];
|
|
921
|
+
let displayColumn = null;
|
|
922
|
+
if (isForeignKey) {
|
|
923
|
+
const refSchema = columnInfo.referencedSchema || columnInfo.referenced_schema;
|
|
924
|
+
const refTable = columnInfo.referencedTable || columnInfo.referenced_table;
|
|
925
|
+
const refColumn = columnInfo.referencedColumn || columnInfo.referenced_column;
|
|
926
|
+
const quotedRefSchema = dialect.quoteId(refSchema);
|
|
927
|
+
const quotedRefTable = dialect.quoteId(refTable);
|
|
928
|
+
const quotedRefColumn = dialect.quoteId(refColumn);
|
|
929
|
+
let columnsToSelect = [];
|
|
930
|
+
if (columnsParam) {
|
|
931
|
+
columnsToSelect = columnsParam.split(",").map((c) => c.trim()).filter((c) => c);
|
|
932
|
+
}
|
|
933
|
+
if (columnsToSelect.length > 0) {
|
|
934
|
+
const selectCols = columnsToSelect.map((col) => dialect.quoteId(col)).join(", ");
|
|
935
|
+
query = `SELECT DISTINCT ${selectCols} FROM ${quotedRefSchema}.${quotedRefTable}`;
|
|
936
|
+
if (searchQuery && searchQuery.trim()) {
|
|
937
|
+
const searchCols = columnsToSelect.map((col) => `${dialect.quoteId(col)} LIKE ${dialect.param(1)}`).join(" OR ");
|
|
938
|
+
query += ` WHERE (${searchCols}) AND ${quotedRefColumn} IS NOT NULL`;
|
|
939
|
+
params.push({ name: "search", value: `%${searchQuery.trim()}%` });
|
|
940
|
+
} else {
|
|
941
|
+
query += ` WHERE ${quotedRefColumn} IS NOT NULL`;
|
|
942
|
+
}
|
|
943
|
+
const orderByCol = columnsToSelect.length > 1 ? columnsToSelect[1] : columnsToSelect[0];
|
|
944
|
+
query += ` ORDER BY ${dialect.quoteId(orderByCol)} ${dialect.limitOffset(0, 1e3)}`;
|
|
945
|
+
} else {
|
|
946
|
+
const refColumnsQuery = `
|
|
947
|
+
SELECT column_name, data_type
|
|
948
|
+
FROM information_schema.columns
|
|
949
|
+
WHERE table_schema = ${dialect.param(1)} AND table_name = ${dialect.param(2)}
|
|
950
|
+
ORDER BY ordinal_position
|
|
951
|
+
`;
|
|
952
|
+
const refColumns = await executeQuery(refColumnsQuery, [
|
|
953
|
+
{ name: "refSchema", value: refSchema },
|
|
954
|
+
{ name: "refTable", value: refTable }
|
|
955
|
+
]);
|
|
956
|
+
const preferredNames = ["name", "title", "description", "code"];
|
|
957
|
+
for (const preferredName of preferredNames) {
|
|
958
|
+
const found = refColumns.find((col) => {
|
|
959
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
960
|
+
return colName.toLowerCase() === preferredName.toLowerCase() && colName.toLowerCase() !== refColumn.toLowerCase();
|
|
961
|
+
});
|
|
962
|
+
if (found) {
|
|
963
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
if (!displayColumn) {
|
|
968
|
+
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
969
|
+
const found = refColumns.find((col) => {
|
|
970
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
971
|
+
const dataType = col.data_type || col.DATA_TYPE;
|
|
972
|
+
return colName.toLowerCase() !== refColumn.toLowerCase() && stringTypes.some((type) => dataType.toLowerCase().includes(type));
|
|
973
|
+
});
|
|
974
|
+
if (found) {
|
|
975
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
const quotedDisplayColumn = displayColumn ? dialect.quoteId(displayColumn) : null;
|
|
979
|
+
const selectCols = displayColumn ? `${quotedRefColumn}, ${quotedDisplayColumn}` : quotedRefColumn;
|
|
980
|
+
query = `SELECT DISTINCT ${selectCols} FROM ${quotedRefSchema}.${quotedRefTable}`;
|
|
981
|
+
if (searchQuery && searchQuery.trim()) {
|
|
982
|
+
if (displayColumn) {
|
|
983
|
+
query += ` WHERE ${quotedDisplayColumn} LIKE ${dialect.param(1)} OR ${quotedRefColumn} LIKE ${dialect.param(1)}`;
|
|
984
|
+
} else {
|
|
985
|
+
query += ` WHERE ${quotedRefColumn} LIKE ${dialect.param(1)}`;
|
|
986
|
+
}
|
|
987
|
+
params.push({ name: "search", value: `%${searchQuery.trim()}%` });
|
|
988
|
+
query += ` AND ${quotedRefColumn} IS NOT NULL`;
|
|
989
|
+
} else {
|
|
990
|
+
query += ` WHERE ${quotedRefColumn} IS NOT NULL`;
|
|
991
|
+
}
|
|
992
|
+
query += ` ORDER BY ${displayColumn ? quotedDisplayColumn : quotedRefColumn} ${dialect.limitOffset(0, 1e3)}`;
|
|
993
|
+
}
|
|
994
|
+
} else {
|
|
995
|
+
const dataType = (columnInfo.data_type || columnInfo.DATA_TYPE || "").toLowerCase();
|
|
996
|
+
const quotedSchema = dialect.quoteId(schema);
|
|
997
|
+
const quotedTable = dialect.quoteId(table);
|
|
998
|
+
const quotedColumn = dialect.quoteId(column);
|
|
999
|
+
const columnsToSelect = columnsParam ? columnsParam.split(",").map((c) => c.trim()).filter((c) => c) : [];
|
|
1000
|
+
if (columnsToSelect.length > 0) {
|
|
1001
|
+
const quotedColumns = columnsToSelect.map((col) => dialect.quoteId(col));
|
|
1002
|
+
const keyColumn = quotedColumns[0];
|
|
1003
|
+
const orderByColumn = quotedColumns.length > 1 ? quotedColumns[1] : quotedColumns[0];
|
|
1004
|
+
query = `SELECT DISTINCT ${quotedColumns.join(", ")} FROM ${quotedSchema}.${quotedTable}`;
|
|
1005
|
+
if (searchQuery && searchQuery.trim()) {
|
|
1006
|
+
const searchCols = quotedColumns.map((col) => `${dialect.tryCastToNVarChar(col)} LIKE ${dialect.param(1)}`).join(" OR ");
|
|
1007
|
+
query += ` WHERE (${searchCols}) AND ${keyColumn} IS NOT NULL`;
|
|
1008
|
+
params.push({ name: "search", value: `%${searchQuery.trim()}%` });
|
|
1009
|
+
} else {
|
|
1010
|
+
query += ` WHERE ${keyColumn} IS NOT NULL`;
|
|
1011
|
+
}
|
|
1012
|
+
query += ` ORDER BY ${orderByColumn} ${dialect.limitOffset(0, 1e3)}`;
|
|
1013
|
+
} else {
|
|
1014
|
+
query = `SELECT DISTINCT ${quotedColumn} FROM ${quotedSchema}.${quotedTable}`;
|
|
1015
|
+
if (searchQuery && searchQuery.trim()) {
|
|
1016
|
+
if (["varchar", "nvarchar", "char", "nchar", "text", "ntext"].some((t) => dataType.includes(t))) {
|
|
1017
|
+
query += ` WHERE ${quotedColumn} LIKE ${dialect.param(1)}`;
|
|
1018
|
+
params.push({ name: "search", value: `%${searchQuery.trim()}%` });
|
|
1019
|
+
query += ` AND ${quotedColumn} IS NOT NULL`;
|
|
1020
|
+
} else {
|
|
1021
|
+
query += ` WHERE ${quotedColumn} IS NOT NULL`;
|
|
1022
|
+
}
|
|
1023
|
+
} else {
|
|
1024
|
+
query += ` WHERE ${quotedColumn} IS NOT NULL`;
|
|
1025
|
+
}
|
|
1026
|
+
query += ` ORDER BY ${quotedColumn} ${dialect.limitOffset(0, 1e3)}`;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
const result = await executeQuery(query, params);
|
|
1030
|
+
res.json(result);
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
console.error("Error fetching distinct values:", error);
|
|
1033
|
+
const errorMessage = error.message || "";
|
|
1034
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
1035
|
+
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
1036
|
+
await disconnect2();
|
|
1037
|
+
}
|
|
1038
|
+
res.status(500).json({ error: error.message || "Failed to fetch distinct values" });
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
642
1041
|
|
|
643
1042
|
// src/server/routes/query.ts
|
|
644
1043
|
import { Router as Router3 } from "express";
|
|
645
1044
|
var queryRoutes = Router3();
|
|
646
1045
|
queryRoutes.post("/", async (req, res) => {
|
|
647
1046
|
try {
|
|
648
|
-
const { query: sqlQuery } = req.body;
|
|
1047
|
+
const { query: sqlQuery, queryId } = req.body;
|
|
649
1048
|
if (!sqlQuery || typeof sqlQuery !== "string") {
|
|
650
1049
|
return res.status(400).json({ error: "Query is required" });
|
|
651
1050
|
}
|
|
1051
|
+
const activeQueryId = queryId || generateQueryId();
|
|
652
1052
|
const startTime = Date.now();
|
|
653
|
-
const { recordsets: resultSets, columnMetadata } = await executeQueryMultiple(sqlQuery);
|
|
1053
|
+
const { recordsets: resultSets, columnMetadata } = await executeQueryMultiple(sqlQuery, void 0, activeQueryId);
|
|
654
1054
|
const executionTime = Date.now() - startTime;
|
|
655
1055
|
res.json({
|
|
656
1056
|
data: resultSets[0] || [],
|
|
657
1057
|
resultSets: resultSets.length > 0 ? resultSets : [],
|
|
658
1058
|
executionTime,
|
|
659
|
-
columnMetadata
|
|
1059
|
+
columnMetadata,
|
|
660
1060
|
// Include column metadata for empty result sets
|
|
1061
|
+
queryId: activeQueryId
|
|
1062
|
+
// Return queryId so client can track it
|
|
661
1063
|
});
|
|
662
1064
|
} catch (error) {
|
|
1065
|
+
if (error.cancelled || error.code === "ECANCEL") {
|
|
1066
|
+
return res.status(499).json({
|
|
1067
|
+
error: "Query was cancelled",
|
|
1068
|
+
cancelled: true
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
663
1071
|
const errorMessage = error.message || "";
|
|
664
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
665
|
-
const { disconnect: disconnect2 } = await import("./
|
|
1072
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
1073
|
+
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
666
1074
|
await disconnect2();
|
|
667
1075
|
}
|
|
668
1076
|
res.status(500).json({
|
|
@@ -671,6 +1079,24 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
671
1079
|
});
|
|
672
1080
|
}
|
|
673
1081
|
});
|
|
1082
|
+
queryRoutes.post("/cancel", async (req, res) => {
|
|
1083
|
+
try {
|
|
1084
|
+
const { queryId } = req.body;
|
|
1085
|
+
if (!queryId || typeof queryId !== "string") {
|
|
1086
|
+
return res.status(400).json({ error: "Query ID is required" });
|
|
1087
|
+
}
|
|
1088
|
+
const cancelled = cancelQuery(queryId);
|
|
1089
|
+
if (cancelled) {
|
|
1090
|
+
res.json({ success: true, message: "Query cancellation requested" });
|
|
1091
|
+
} else {
|
|
1092
|
+
res.status(404).json({ error: "Query not found or already completed" });
|
|
1093
|
+
}
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
res.status(500).json({
|
|
1096
|
+
error: error.message || "Failed to cancel query"
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
674
1100
|
|
|
675
1101
|
// src/server/index.ts
|
|
676
1102
|
var __filename = fileURLToPath(import.meta.url);
|