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