datapeek 0.1.11 → 0.1.13
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/README.md +38 -9
- 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 +449 -374
- package/dist/cli/index.js +452 -376
- package/dist/cli/queryCancellation-4CMKYTFU.js +46 -0
- package/dist/cli/queryCancellation-E5O4FBUY.js +14 -0
- package/dist/client/assets/index-H7ji6dtA.js +385 -0
- package/dist/client/assets/index-TFw4Z1Ky.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/client/screenshots/dark1.png +0 -0
- package/dist/client/screenshots/dark2.png +0 -0
- package/dist/client/screenshots/light1.png +0 -0
- package/dist/client/screenshots/light2.png +0 -0
- 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 +449 -374
- package/dist/server/index.js +449 -374
- 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-CPoX5Biw.css +0 -1
- package/dist/client/assets/index-KtK9Gzwi.js +0 -375
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) {
|
|
120
141
|
return res.status(400).json({ error: "Not connected to database" });
|
|
121
142
|
}
|
|
143
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
144
|
+
if (!isConnected) {
|
|
145
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
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) {
|
|
146
174
|
return res.status(400).json({ error: "Not connected to database" });
|
|
147
175
|
}
|
|
176
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
177
|
+
if (!isConnected) {
|
|
178
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
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" });
|
|
@@ -262,28 +295,35 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
262
295
|
}
|
|
263
296
|
console.log("Parsed filters:", parsedFilters);
|
|
264
297
|
const pool = getConnection();
|
|
265
|
-
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) {
|
|
266
303
|
return res.status(400).json({ error: "Not connected to database" });
|
|
267
304
|
}
|
|
305
|
+
const dialect = getDialect();
|
|
268
306
|
const columnMetadata = {};
|
|
269
307
|
if (parsedFilters.length > 0) {
|
|
270
308
|
try {
|
|
271
309
|
const columnNames = [...new Set(parsedFilters.map((f) => f.column))];
|
|
272
|
-
const placeholders = columnNames.map((_, i) =>
|
|
310
|
+
const placeholders = columnNames.map((_, i) => dialect.param(i + 3)).join(", ");
|
|
273
311
|
const validateQuery = `
|
|
274
|
-
SELECT
|
|
275
|
-
FROM
|
|
276
|
-
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})
|
|
277
315
|
`;
|
|
278
316
|
const validateParams = [
|
|
279
|
-
{ name: "schema", value: schema
|
|
280
|
-
{ name: "table", value: table
|
|
281
|
-
...columnNames.map((col
|
|
317
|
+
{ name: "schema", value: schema },
|
|
318
|
+
{ name: "table", value: table },
|
|
319
|
+
...columnNames.map((col) => ({ name: "col", value: col }))
|
|
282
320
|
];
|
|
283
321
|
const validateResult = await executeQuery(validateQuery, validateParams);
|
|
284
322
|
validateResult.forEach((r) => {
|
|
285
|
-
|
|
286
|
-
|
|
323
|
+
const colName = r.column_name || r.COLUMN_NAME;
|
|
324
|
+
const dataType = r.data_type || r.DATA_TYPE;
|
|
325
|
+
columnMetadata[colName] = {
|
|
326
|
+
dataType,
|
|
287
327
|
exists: true
|
|
288
328
|
};
|
|
289
329
|
});
|
|
@@ -292,24 +332,9 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
292
332
|
console.error("Error validating filter columns:", e);
|
|
293
333
|
}
|
|
294
334
|
}
|
|
295
|
-
const getSqlType = (dataType) => {
|
|
296
|
-
const dt = dataType.toLowerCase();
|
|
297
|
-
if (dt === "int" || dt === "integer") return sql.Int;
|
|
298
|
-
if (dt === "bigint") return sql.BigInt;
|
|
299
|
-
if (dt === "smallint") return sql.SmallInt;
|
|
300
|
-
if (dt === "tinyint") return sql.TinyInt;
|
|
301
|
-
if (dt === "bit") return sql.Bit;
|
|
302
|
-
if (dt === "float" || dt === "real" || dt === "double precision") return sql.Float;
|
|
303
|
-
if (dt === "decimal" || dt === "numeric" || dt === "money" || dt === "smallmoney") return sql.Decimal(18, 2);
|
|
304
|
-
if (dt === "datetime" || dt === "datetime2" || dt === "smalldatetime") return sql.DateTime;
|
|
305
|
-
if (dt === "date") return sql.Date;
|
|
306
|
-
if (dt === "time") return sql.Time;
|
|
307
|
-
if (dt === "uniqueidentifier") return sql.UniqueIdentifier;
|
|
308
|
-
return sql.NVarChar;
|
|
309
|
-
};
|
|
310
335
|
let whereClause = "";
|
|
311
336
|
const filterParams = [];
|
|
312
|
-
let paramIndex =
|
|
337
|
+
let paramIndex = 1;
|
|
313
338
|
if (parsedFilters.length > 0) {
|
|
314
339
|
const whereConditions = [];
|
|
315
340
|
parsedFilters.forEach((filter) => {
|
|
@@ -325,83 +350,82 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
325
350
|
if (value === null || value === void 0 || value === "") {
|
|
326
351
|
return;
|
|
327
352
|
}
|
|
328
|
-
const
|
|
329
|
-
const paramName = `filter${paramIndex}`;
|
|
353
|
+
const quotedColumn = dialect.quoteId(columnName);
|
|
330
354
|
try {
|
|
331
355
|
let condition = "";
|
|
332
356
|
switch (operator) {
|
|
333
357
|
// Text operators
|
|
334
358
|
case "contains":
|
|
335
|
-
filterParams.push({ name:
|
|
336
|
-
condition =
|
|
359
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}%` });
|
|
360
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
337
361
|
break;
|
|
338
362
|
case "equals":
|
|
339
|
-
filterParams.push({ name:
|
|
340
|
-
condition =
|
|
363
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
364
|
+
condition = `${quotedColumn} = ${dialect.param(paramIndex)}`;
|
|
341
365
|
break;
|
|
342
366
|
case "startsWith":
|
|
343
|
-
filterParams.push({ name:
|
|
344
|
-
condition =
|
|
367
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `${String(value)}%` });
|
|
368
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
345
369
|
break;
|
|
346
370
|
case "endsWith":
|
|
347
|
-
filterParams.push({ name:
|
|
348
|
-
condition =
|
|
371
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}` });
|
|
372
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
349
373
|
break;
|
|
350
374
|
case "notContains":
|
|
351
|
-
filterParams.push({ name:
|
|
352
|
-
condition =
|
|
375
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}%` });
|
|
376
|
+
condition = `${quotedColumn} NOT LIKE ${dialect.param(paramIndex)}`;
|
|
353
377
|
break;
|
|
354
378
|
// Number operators
|
|
355
379
|
case "eq":
|
|
356
|
-
filterParams.push({ name:
|
|
357
|
-
condition =
|
|
380
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
381
|
+
condition = `${quotedColumn} = ${dialect.param(paramIndex)}`;
|
|
358
382
|
break;
|
|
359
383
|
case "gt":
|
|
360
|
-
filterParams.push({ name:
|
|
361
|
-
condition =
|
|
384
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
385
|
+
condition = `${quotedColumn} > ${dialect.param(paramIndex)}`;
|
|
362
386
|
break;
|
|
363
387
|
case "gte":
|
|
364
|
-
filterParams.push({ name:
|
|
365
|
-
condition =
|
|
388
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
389
|
+
condition = `${quotedColumn} >= ${dialect.param(paramIndex)}`;
|
|
366
390
|
break;
|
|
367
391
|
case "lt":
|
|
368
|
-
filterParams.push({ name:
|
|
369
|
-
condition =
|
|
392
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
393
|
+
condition = `${quotedColumn} < ${dialect.param(paramIndex)}`;
|
|
370
394
|
break;
|
|
371
395
|
case "lte":
|
|
372
|
-
filterParams.push({ name:
|
|
373
|
-
condition =
|
|
396
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
397
|
+
condition = `${quotedColumn} <= ${dialect.param(paramIndex)}`;
|
|
374
398
|
break;
|
|
375
399
|
case "between":
|
|
376
400
|
if (typeof value === "object" && "from" in value && "to" in value) {
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
filterParams.push({ name:
|
|
380
|
-
filterParams.push({ name:
|
|
381
|
-
condition =
|
|
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)}`;
|
|
382
406
|
paramIndex++;
|
|
383
407
|
}
|
|
384
408
|
break;
|
|
385
409
|
// Date operators
|
|
386
410
|
case "dateEq":
|
|
387
|
-
filterParams.push({ name:
|
|
388
|
-
condition =
|
|
411
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
412
|
+
condition = `${dialect.castToDate(quotedColumn)} = ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
389
413
|
break;
|
|
390
414
|
case "dateAfter":
|
|
391
|
-
filterParams.push({ name:
|
|
392
|
-
condition =
|
|
415
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
416
|
+
condition = `${dialect.castToDate(quotedColumn)} > ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
393
417
|
break;
|
|
394
418
|
case "dateBefore":
|
|
395
|
-
filterParams.push({ name:
|
|
396
|
-
condition =
|
|
419
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
420
|
+
condition = `${dialect.castToDate(quotedColumn)} < ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
397
421
|
break;
|
|
398
422
|
case "dateBetween":
|
|
399
423
|
if (typeof value === "object" && "from" in value && "to" in value) {
|
|
400
|
-
const
|
|
401
|
-
const
|
|
402
|
-
filterParams.push({ name:
|
|
403
|
-
filterParams.push({ name:
|
|
404
|
-
condition =
|
|
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))}`;
|
|
405
429
|
paramIndex++;
|
|
406
430
|
}
|
|
407
431
|
break;
|
|
@@ -409,19 +433,21 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
409
433
|
case "in":
|
|
410
434
|
case "notIn":
|
|
411
435
|
if (Array.isArray(value) && value.length > 0) {
|
|
412
|
-
const placeholders =
|
|
436
|
+
const placeholders = [];
|
|
413
437
|
value.forEach((val, i) => {
|
|
414
|
-
|
|
438
|
+
const currentIndex = paramIndex + i;
|
|
439
|
+
filterParams.push({ name: `filter${currentIndex}`, value: val });
|
|
440
|
+
placeholders.push(dialect.param(currentIndex));
|
|
415
441
|
});
|
|
416
442
|
const inOperator = operator === "in" ? "IN" : "NOT IN";
|
|
417
|
-
condition =
|
|
443
|
+
condition = `${quotedColumn} ${inOperator} (${placeholders.join(", ")})`;
|
|
418
444
|
paramIndex += value.length - 1;
|
|
419
445
|
}
|
|
420
446
|
break;
|
|
421
447
|
default:
|
|
422
448
|
console.warn(`Unknown filter operator: ${operator}, falling back to contains`);
|
|
423
|
-
filterParams.push({ name:
|
|
424
|
-
condition =
|
|
449
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}%` });
|
|
450
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
425
451
|
}
|
|
426
452
|
if (condition) {
|
|
427
453
|
whereConditions.push(condition);
|
|
@@ -438,11 +464,13 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
438
464
|
}
|
|
439
465
|
}
|
|
440
466
|
const qualifiedDataWhereClause = parsedFilters.reduce((clause, filter) => {
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
return clause.replace(
|
|
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);
|
|
444
470
|
}, whereClause);
|
|
445
|
-
const
|
|
471
|
+
const quotedSchema = dialect.quoteId(schema);
|
|
472
|
+
const quotedTable = dialect.quoteId(table);
|
|
473
|
+
const countQuery = `SELECT COUNT(*) as total FROM ${quotedSchema}.${quotedTable}${whereClause ? " " + whereClause : ""}`;
|
|
446
474
|
const countResult = await executeQuery(countQuery, filterParams.length > 0 ? filterParams : []);
|
|
447
475
|
const total = countResult[0]?.total || 0;
|
|
448
476
|
let orderByColumn = sortColumn || "";
|
|
@@ -450,17 +478,19 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
450
478
|
if (orderByColumn) {
|
|
451
479
|
try {
|
|
452
480
|
const validateQuery = `
|
|
453
|
-
SELECT
|
|
454
|
-
FROM
|
|
455
|
-
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)}
|
|
456
484
|
`;
|
|
457
485
|
const validateResult = await executeQuery(validateQuery, [
|
|
458
|
-
{ name: "schema", value: schema
|
|
459
|
-
{ name: "table", value: table
|
|
460
|
-
{ name: "column", value: orderByColumn
|
|
486
|
+
{ name: "schema", value: schema },
|
|
487
|
+
{ name: "table", value: table },
|
|
488
|
+
{ name: "column", value: orderByColumn }
|
|
461
489
|
]);
|
|
462
490
|
if (validateResult.length === 0) {
|
|
463
491
|
orderByColumn = "";
|
|
492
|
+
} else {
|
|
493
|
+
orderByColumn = validateResult[0].column_name || validateResult[0].COLUMN_NAME || orderByColumn;
|
|
464
494
|
}
|
|
465
495
|
} catch (e) {
|
|
466
496
|
orderByColumn = "";
|
|
@@ -468,18 +498,20 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
468
498
|
}
|
|
469
499
|
if (!orderByColumn) {
|
|
470
500
|
try {
|
|
501
|
+
const topClause = dialect.topN(1);
|
|
471
502
|
const structureQuery = `
|
|
472
|
-
SELECT
|
|
473
|
-
FROM
|
|
474
|
-
WHERE
|
|
475
|
-
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) : ""}
|
|
476
508
|
`;
|
|
477
509
|
const structureResult = await executeQuery(structureQuery, [
|
|
478
|
-
{ name: "schema", value: schema
|
|
479
|
-
{ name: "table", value: table
|
|
510
|
+
{ name: "schema", value: schema },
|
|
511
|
+
{ name: "table", value: table }
|
|
480
512
|
]);
|
|
481
513
|
if (structureResult.length > 0) {
|
|
482
|
-
orderByColumn = structureResult[0].COLUMN_NAME;
|
|
514
|
+
orderByColumn = structureResult[0].column_name || structureResult[0].COLUMN_NAME;
|
|
483
515
|
}
|
|
484
516
|
} catch (e) {
|
|
485
517
|
}
|
|
@@ -489,48 +521,48 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
489
521
|
const fkDisplayColumns = {};
|
|
490
522
|
const fkQuery = `
|
|
491
523
|
SELECT
|
|
492
|
-
kcu1.
|
|
493
|
-
kcu2.
|
|
494
|
-
kcu2.
|
|
495
|
-
kcu2.
|
|
496
|
-
FROM
|
|
497
|
-
INNER JOIN
|
|
498
|
-
ON rc.
|
|
499
|
-
AND rc.
|
|
500
|
-
AND rc.
|
|
501
|
-
INNER JOIN
|
|
502
|
-
ON rc.
|
|
503
|
-
AND rc.
|
|
504
|
-
AND rc.
|
|
505
|
-
AND kcu1.
|
|
506
|
-
WHERE kcu1.
|
|
507
|
-
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)}
|
|
508
540
|
`;
|
|
509
541
|
console.log("Fetching foreign keys with query:", fkQuery);
|
|
510
542
|
const foreignKeys = await executeQuery(fkQuery, [
|
|
511
|
-
{ name: "schema", value: schema
|
|
512
|
-
{ name: "table", value: table
|
|
543
|
+
{ name: "schema", value: schema },
|
|
544
|
+
{ name: "table", value: table }
|
|
513
545
|
]);
|
|
514
546
|
console.log(`Found ${foreignKeys.length} foreign key(s)`);
|
|
515
547
|
if (foreignKeys.length > 0) {
|
|
516
548
|
const uniqueRefTables = Array.from(
|
|
517
|
-
new Set(foreignKeys.map((fk) => `${fk.referencedSchema}.${fk.referencedTable}`))
|
|
549
|
+
new Set(foreignKeys.map((fk) => `${fk.referencedSchema || fk.referenced_schema}.${fk.referencedTable || fk.referenced_table}`))
|
|
518
550
|
);
|
|
519
551
|
const tableConditions = uniqueRefTables.map((tableRef, idx) => {
|
|
520
552
|
const [refSchema, refTable] = tableRef.split(".");
|
|
521
|
-
return `(
|
|
553
|
+
return `(table_schema = ${dialect.param(idx * 2 + 1)} AND table_name = ${dialect.param(idx * 2 + 2)})`;
|
|
522
554
|
}).join(" OR ");
|
|
523
555
|
const batchColumnsQuery = `
|
|
524
|
-
SELECT
|
|
525
|
-
FROM
|
|
556
|
+
SELECT table_schema, table_name, column_name, data_type, ordinal_position
|
|
557
|
+
FROM information_schema.columns
|
|
526
558
|
WHERE ${tableConditions}
|
|
527
|
-
ORDER BY
|
|
559
|
+
ORDER BY table_schema, table_name, ordinal_position
|
|
528
560
|
`;
|
|
529
|
-
const batchParams = uniqueRefTables.flatMap((tableRef
|
|
561
|
+
const batchParams = uniqueRefTables.flatMap((tableRef) => {
|
|
530
562
|
const [refSchema, refTable] = tableRef.split(".");
|
|
531
563
|
return [
|
|
532
|
-
{ name:
|
|
533
|
-
{ name:
|
|
564
|
+
{ name: "refSchema", value: refSchema },
|
|
565
|
+
{ name: "refTable", value: refTable }
|
|
534
566
|
];
|
|
535
567
|
});
|
|
536
568
|
console.log("Fetching referenced table columns with query:", batchColumnsQuery);
|
|
@@ -539,37 +571,41 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
539
571
|
console.log(`Found columns for ${uniqueRefTables.length} referenced table(s)`);
|
|
540
572
|
const columnsByTable = {};
|
|
541
573
|
allRefColumns.forEach((col) => {
|
|
542
|
-
const
|
|
574
|
+
const schemaName = col.table_schema || col.TABLE_SCHEMA;
|
|
575
|
+
const tableName = col.table_name || col.TABLE_NAME;
|
|
576
|
+
const key = `${schemaName}.${tableName}`;
|
|
543
577
|
if (!columnsByTable[key]) {
|
|
544
578
|
columnsByTable[key] = [];
|
|
545
579
|
}
|
|
546
580
|
columnsByTable[key].push(col);
|
|
547
581
|
});
|
|
548
582
|
for (const fk of foreignKeys) {
|
|
549
|
-
const fkColumn = fk.fkColumnName;
|
|
550
|
-
const refSchema = fk.referencedSchema;
|
|
551
|
-
const refTable = fk.referencedTable;
|
|
552
|
-
const refColumn = fk.referencedColumn;
|
|
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;
|
|
553
587
|
const tableKey = `${refSchema}.${refTable}`;
|
|
554
588
|
const refColumns = columnsByTable[tableKey] || [];
|
|
555
589
|
const preferredNames = ["name", "title", "description", "code"];
|
|
556
590
|
let displayColumn = null;
|
|
557
591
|
for (const preferredName of preferredNames) {
|
|
558
|
-
const found = refColumns.find(
|
|
559
|
-
|
|
560
|
-
|
|
592
|
+
const found = refColumns.find((col) => {
|
|
593
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
594
|
+
return colName.toLowerCase() === preferredName.toLowerCase();
|
|
595
|
+
});
|
|
561
596
|
if (found) {
|
|
562
|
-
displayColumn = found.COLUMN_NAME;
|
|
597
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
563
598
|
break;
|
|
564
599
|
}
|
|
565
600
|
}
|
|
566
601
|
if (!displayColumn) {
|
|
567
602
|
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
568
|
-
const found = refColumns.find(
|
|
569
|
-
|
|
570
|
-
|
|
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
|
+
});
|
|
571
607
|
if (found) {
|
|
572
|
-
displayColumn = found.COLUMN_NAME;
|
|
608
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
573
609
|
}
|
|
574
610
|
}
|
|
575
611
|
if (displayColumn) {
|
|
@@ -583,89 +619,79 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
583
619
|
refColumn,
|
|
584
620
|
displayColumn
|
|
585
621
|
});
|
|
586
|
-
fkSelects.push(`${alias}.
|
|
622
|
+
fkSelects.push(`${dialect.quoteId(alias)}.${dialect.quoteId(displayColumn)} as ${dialect.quoteId(`${fkColumn}_display`)}`);
|
|
587
623
|
}
|
|
588
624
|
fkDisplayColumns[fkColumn] = displayColumn;
|
|
589
625
|
}
|
|
590
626
|
}
|
|
591
627
|
}
|
|
592
628
|
const baseTableAlias = "t";
|
|
593
|
-
|
|
629
|
+
const quotedTableAlias = dialect.quoteId(baseTableAlias);
|
|
630
|
+
let baseSelects = `${quotedTableAlias}.*`;
|
|
594
631
|
if (fkDisplayMode === "display-only") {
|
|
595
632
|
const fkColumnNames = fkJoins.map((fk) => fk.fkColumn);
|
|
596
633
|
if (fkColumnNames.length > 0) {
|
|
597
|
-
baseSelects =
|
|
634
|
+
baseSelects = `${quotedTableAlias}.*`;
|
|
598
635
|
}
|
|
599
636
|
}
|
|
600
637
|
const allSelects = `${baseSelects}${fkSelects.length > 0 ? ", " + fkSelects.join(", ") : ""}`;
|
|
601
638
|
const buildJoinClauses = (tableAlias) => {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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 ");
|
|
605
648
|
};
|
|
606
649
|
let data;
|
|
607
650
|
let generatedQuery = "";
|
|
651
|
+
const offsetParamIndex = filterParams.length + 1;
|
|
652
|
+
const pageSizeParamIndex = filterParams.length + 2;
|
|
608
653
|
if (orderByColumn) {
|
|
654
|
+
const quotedOrderByColumn = dialect.quoteId(orderByColumn);
|
|
655
|
+
const limitOffsetClause = dialect.limitOffset(offset, pageSize);
|
|
609
656
|
const dataQuery = `
|
|
610
657
|
SELECT ${allSelects}
|
|
611
|
-
FROM
|
|
658
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}
|
|
612
659
|
${buildJoinClauses(baseTableAlias)}
|
|
613
660
|
${qualifiedDataWhereClause}
|
|
614
|
-
ORDER BY ${
|
|
615
|
-
|
|
616
|
-
FETCH NEXT @pageSize ROWS ONLY
|
|
661
|
+
ORDER BY ${quotedTableAlias}.${quotedOrderByColumn} ${orderByDirection}
|
|
662
|
+
${limitOffsetClause}
|
|
617
663
|
`;
|
|
618
664
|
generatedQuery = `SELECT ${allSelects}
|
|
619
|
-
FROM
|
|
620
|
-
ORDER BY ${
|
|
621
|
-
|
|
622
|
-
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}`;
|
|
623
668
|
console.log("Executing SQL query:", dataQuery);
|
|
624
669
|
console.log("Query parameters:", {
|
|
625
670
|
offset,
|
|
626
671
|
pageSize,
|
|
627
672
|
filterParams: filterParams.map((p) => ({ name: p.name, value: p.value }))
|
|
628
673
|
});
|
|
629
|
-
data = await executeQuery(dataQuery,
|
|
630
|
-
{ name: "offset", value: offset, type: sql.Int },
|
|
631
|
-
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
632
|
-
...filterParams
|
|
633
|
-
]);
|
|
674
|
+
data = await executeQuery(dataQuery, filterParams);
|
|
634
675
|
} else {
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
FROM [${schema}].[${table}]
|
|
638
|
-
${whereClause}
|
|
639
|
-
`;
|
|
676
|
+
const dbType = getDbType();
|
|
677
|
+
const limitOffsetClause = dialect.limitOffset(offset, pageSize);
|
|
640
678
|
const dataQuery = `
|
|
641
679
|
SELECT ${allSelects}
|
|
642
|
-
FROM
|
|
680
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}
|
|
643
681
|
${buildJoinClauses(baseTableAlias)}
|
|
644
|
-
|
|
645
|
-
|
|
682
|
+
${qualifiedDataWhereClause}
|
|
683
|
+
${limitOffsetClause}
|
|
646
684
|
`;
|
|
647
685
|
generatedQuery = `SELECT ${allSelects}
|
|
648
|
-
FROM (
|
|
649
|
-
|
|
650
|
-
FROM [${schema}].[${table}]${whereClause ? "\n " + whereClause : ""}
|
|
651
|
-
) ${baseTableAlias}${fkJoins.length > 0 ? "\n" + buildJoinClauses(baseTableAlias) : ""}
|
|
652
|
-
WHERE ${baseTableAlias}.rn > ${offset} AND ${baseTableAlias}.rn <= ${offset + pageSize}
|
|
653
|
-
ORDER BY ${baseTableAlias}.rn`;
|
|
686
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}${fkJoins.length > 0 ? "\n" + buildJoinClauses(baseTableAlias) : ""}${qualifiedDataWhereClause ? "\n" + qualifiedDataWhereClause : ""}
|
|
687
|
+
${limitOffsetClause}`;
|
|
654
688
|
console.log("Executing SQL query:", dataQuery);
|
|
655
689
|
console.log("Query parameters:", {
|
|
656
690
|
offset,
|
|
657
691
|
pageSize,
|
|
658
692
|
filterParams: filterParams.map((p) => ({ name: p.name, value: p.value }))
|
|
659
693
|
});
|
|
660
|
-
data = await executeQuery(dataQuery,
|
|
661
|
-
{ name: "offset", value: offset, type: sql.Int },
|
|
662
|
-
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
663
|
-
...filterParams
|
|
664
|
-
]);
|
|
665
|
-
data = data.map((row) => {
|
|
666
|
-
const { rn, ...rest } = row;
|
|
667
|
-
return rest;
|
|
668
|
-
});
|
|
694
|
+
data = await executeQuery(dataQuery, filterParams);
|
|
669
695
|
}
|
|
670
696
|
if (fkDisplayMode === "display-only") {
|
|
671
697
|
const fkColumnNames = fkJoins.map((fk) => fk.fkColumn);
|
|
@@ -693,10 +719,22 @@ ORDER BY ${baseTableAlias}.rn`;
|
|
|
693
719
|
const paramValues = new Map(
|
|
694
720
|
filterParams.map((p) => [p.name, p.value])
|
|
695
721
|
);
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
+
}
|
|
700
738
|
}
|
|
701
739
|
res.json({
|
|
702
740
|
data,
|
|
@@ -713,8 +751,8 @@ ORDER BY ${baseTableAlias}.rn`;
|
|
|
713
751
|
} catch (error) {
|
|
714
752
|
console.error("Error fetching table data:", error);
|
|
715
753
|
const errorMessage = error.message || "";
|
|
716
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
717
|
-
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");
|
|
718
756
|
await disconnect2();
|
|
719
757
|
}
|
|
720
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";
|
|
@@ -741,73 +779,68 @@ tableRoutes.post("/:schema/:table/related-data", async (req, res) => {
|
|
|
741
779
|
return res.status(400).json({ error: "Missing required parameters" });
|
|
742
780
|
}
|
|
743
781
|
const pool = getConnection();
|
|
744
|
-
if (!pool
|
|
782
|
+
if (!pool) {
|
|
745
783
|
return res.status(400).json({ error: "Not connected to database" });
|
|
746
784
|
}
|
|
785
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
786
|
+
if (!isConnected) {
|
|
787
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
788
|
+
}
|
|
789
|
+
const dialect = getDialect();
|
|
747
790
|
const columnsQuery = `
|
|
748
|
-
SELECT
|
|
749
|
-
FROM
|
|
750
|
-
WHERE
|
|
751
|
-
AND
|
|
752
|
-
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
|
|
753
796
|
`;
|
|
754
797
|
const columns = await executeQuery(columnsQuery, [
|
|
755
|
-
{ name: "refSchema", value: referencedSchema
|
|
756
|
-
{ name: "refTable", value: referencedTable
|
|
798
|
+
{ name: "refSchema", value: referencedSchema },
|
|
799
|
+
{ name: "refTable", value: referencedTable }
|
|
757
800
|
]);
|
|
758
|
-
const referencedColInfo = columns.find(
|
|
759
|
-
|
|
760
|
-
|
|
801
|
+
const referencedColInfo = columns.find((col) => {
|
|
802
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
803
|
+
return colName === referencedColumn;
|
|
804
|
+
});
|
|
761
805
|
if (!referencedColInfo) {
|
|
762
806
|
return res.status(400).json({ error: `Referenced column '${referencedColumn}' not found` });
|
|
763
807
|
}
|
|
764
|
-
const getSqlType = (dataType) => {
|
|
765
|
-
const dt = dataType.toLowerCase();
|
|
766
|
-
if (dt === "int" || dt === "integer") return sql.Int;
|
|
767
|
-
if (dt === "bigint") return sql.BigInt;
|
|
768
|
-
if (dt === "smallint") return sql.SmallInt;
|
|
769
|
-
if (dt === "tinyint") return sql.TinyInt;
|
|
770
|
-
if (dt === "bit") return sql.Bit;
|
|
771
|
-
if (dt === "float" || dt === "real" || dt === "double precision") return sql.Float;
|
|
772
|
-
if (dt === "decimal" || dt === "numeric" || dt === "money" || dt === "smallmoney") return sql.Decimal(18, 0);
|
|
773
|
-
if (dt === "datetime" || dt === "datetime2" || dt === "smalldatetime") return sql.DateTime;
|
|
774
|
-
if (dt === "date") return sql.Date;
|
|
775
|
-
if (dt === "time") return sql.Time;
|
|
776
|
-
if (dt === "uniqueidentifier") return sql.UniqueIdentifier;
|
|
777
|
-
return sql.NVarChar;
|
|
778
|
-
};
|
|
779
|
-
const referencedColumnType = getSqlType(referencedColInfo.DATA_TYPE);
|
|
780
808
|
const preferredNames = ["name", "title", "description", "code"];
|
|
781
809
|
let displayColumn = null;
|
|
782
810
|
for (const preferredName of preferredNames) {
|
|
783
|
-
const found = columns.find(
|
|
784
|
-
|
|
785
|
-
|
|
811
|
+
const found = columns.find((col) => {
|
|
812
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
813
|
+
return colName.toLowerCase() === preferredName.toLowerCase();
|
|
814
|
+
});
|
|
786
815
|
if (found) {
|
|
787
|
-
displayColumn = found.COLUMN_NAME;
|
|
816
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
788
817
|
break;
|
|
789
818
|
}
|
|
790
819
|
}
|
|
791
820
|
if (!displayColumn) {
|
|
792
821
|
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
793
|
-
const found = columns.find(
|
|
794
|
-
|
|
795
|
-
|
|
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
|
+
});
|
|
796
826
|
if (found) {
|
|
797
|
-
displayColumn = found.COLUMN_NAME;
|
|
827
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
798
828
|
}
|
|
799
829
|
}
|
|
800
|
-
const
|
|
801
|
-
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;
|
|
802
836
|
const dataQuery = `
|
|
803
837
|
SELECT ${selectColumns}
|
|
804
|
-
FROM
|
|
805
|
-
WHERE
|
|
838
|
+
FROM ${quotedRefSchema}.${quotedRefTable}
|
|
839
|
+
WHERE ${quotedRefColumn} IN (${placeholders})
|
|
806
840
|
`;
|
|
807
|
-
const params = ids.map((id
|
|
808
|
-
name:
|
|
809
|
-
value: id
|
|
810
|
-
type: referencedColumnType
|
|
841
|
+
const params = ids.map((id) => ({
|
|
842
|
+
name: "id",
|
|
843
|
+
value: id
|
|
811
844
|
}));
|
|
812
845
|
const result = await executeQuery(dataQuery, params);
|
|
813
846
|
const dataMap = {};
|
|
@@ -819,8 +852,8 @@ tableRoutes.post("/:schema/:table/related-data", async (req, res) => {
|
|
|
819
852
|
} catch (error) {
|
|
820
853
|
console.error("Error fetching related data:", error);
|
|
821
854
|
const errorMessage = error.message || "";
|
|
822
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
823
|
-
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");
|
|
824
857
|
await disconnect2();
|
|
825
858
|
}
|
|
826
859
|
res.status(500).json({ error: error.message || "Failed to fetch related data" });
|
|
@@ -832,148 +865,163 @@ tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
|
832
865
|
const searchQuery = req.query.search;
|
|
833
866
|
const columnsParam = req.query.columns;
|
|
834
867
|
const pool = getConnection();
|
|
835
|
-
if (!pool
|
|
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) {
|
|
836
873
|
return res.status(400).json({ error: "Not connected to database" });
|
|
837
874
|
}
|
|
875
|
+
const dialect = getDialect();
|
|
838
876
|
const columnQuery = `
|
|
839
877
|
SELECT
|
|
840
|
-
c.
|
|
841
|
-
c.
|
|
842
|
-
fk.
|
|
843
|
-
fk.
|
|
844
|
-
fk.
|
|
845
|
-
FROM
|
|
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
|
|
846
884
|
LEFT JOIN (
|
|
847
885
|
SELECT
|
|
848
|
-
kcu1.
|
|
849
|
-
kcu1.
|
|
850
|
-
kcu1.
|
|
851
|
-
kcu2.
|
|
852
|
-
kcu2.
|
|
853
|
-
kcu2.
|
|
854
|
-
FROM
|
|
855
|
-
INNER JOIN
|
|
856
|
-
ON rc.
|
|
857
|
-
AND rc.
|
|
858
|
-
AND rc.
|
|
859
|
-
INNER JOIN
|
|
860
|
-
ON rc.
|
|
861
|
-
AND rc.
|
|
862
|
-
AND rc.
|
|
863
|
-
AND kcu1.
|
|
864
|
-
) fk ON c.
|
|
865
|
-
AND c.
|
|
866
|
-
AND c.
|
|
867
|
-
WHERE c.
|
|
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)}
|
|
868
906
|
`;
|
|
869
907
|
const columnResult = await executeQuery(columnQuery, [
|
|
870
|
-
{ name: "schema", value: schema
|
|
871
|
-
{ name: "table", value: table
|
|
872
|
-
{ name: "column", value: column
|
|
908
|
+
{ name: "schema", value: schema },
|
|
909
|
+
{ name: "table", value: table },
|
|
910
|
+
{ name: "column", value: column }
|
|
873
911
|
]);
|
|
874
912
|
if (columnResult.length === 0) {
|
|
875
913
|
return res.status(400).json({ error: "Column not found" });
|
|
876
914
|
}
|
|
877
915
|
const columnInfo = columnResult[0];
|
|
878
|
-
const isForeignKey = !!columnInfo.referencedSchema && !!columnInfo.referencedTable;
|
|
916
|
+
const isForeignKey = !!(columnInfo.referencedSchema || columnInfo.referenced_schema) && !!(columnInfo.referencedTable || columnInfo.referenced_table);
|
|
879
917
|
let query = "";
|
|
880
918
|
const params = [];
|
|
881
919
|
let displayColumn = null;
|
|
882
920
|
if (isForeignKey) {
|
|
883
|
-
const refSchema = columnInfo.referencedSchema;
|
|
884
|
-
const refTable = columnInfo.referencedTable;
|
|
885
|
-
const refColumn = columnInfo.referencedColumn;
|
|
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);
|
|
886
927
|
let columnsToSelect = [];
|
|
887
928
|
if (columnsParam) {
|
|
888
929
|
columnsToSelect = columnsParam.split(",").map((c) => c.trim()).filter((c) => c);
|
|
889
930
|
}
|
|
890
931
|
if (columnsToSelect.length > 0) {
|
|
891
|
-
const selectCols = columnsToSelect.map((col) =>
|
|
892
|
-
query = `SELECT DISTINCT ${selectCols} FROM
|
|
932
|
+
const selectCols = columnsToSelect.map((col) => dialect.quoteId(col)).join(", ");
|
|
933
|
+
query = `SELECT DISTINCT ${selectCols} FROM ${quotedRefSchema}.${quotedRefTable}`;
|
|
893
934
|
if (searchQuery && searchQuery.trim()) {
|
|
894
|
-
const searchCols = columnsToSelect.map((col) =>
|
|
895
|
-
query += ` WHERE (${searchCols}) AND
|
|
896
|
-
params.push({ name: "search", value: `%${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()}%` });
|
|
897
938
|
} else {
|
|
898
|
-
query += ` WHERE
|
|
939
|
+
query += ` WHERE ${quotedRefColumn} IS NOT NULL`;
|
|
899
940
|
}
|
|
900
941
|
const orderByCol = columnsToSelect.length > 1 ? columnsToSelect[1] : columnsToSelect[0];
|
|
901
|
-
query += ` ORDER BY
|
|
942
|
+
query += ` ORDER BY ${dialect.quoteId(orderByCol)} ${dialect.limitOffset(0, 1e3)}`;
|
|
902
943
|
} else {
|
|
903
944
|
const refColumnsQuery = `
|
|
904
|
-
SELECT
|
|
905
|
-
FROM
|
|
906
|
-
WHERE
|
|
907
|
-
ORDER BY
|
|
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
|
|
908
949
|
`;
|
|
909
950
|
const refColumns = await executeQuery(refColumnsQuery, [
|
|
910
|
-
{ name: "refSchema", value: refSchema
|
|
911
|
-
{ name: "refTable", value: refTable
|
|
951
|
+
{ name: "refSchema", value: refSchema },
|
|
952
|
+
{ name: "refTable", value: refTable }
|
|
912
953
|
]);
|
|
913
954
|
const preferredNames = ["name", "title", "description", "code"];
|
|
914
955
|
for (const preferredName of preferredNames) {
|
|
915
|
-
const found = refColumns.find(
|
|
916
|
-
|
|
917
|
-
|
|
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
|
+
});
|
|
918
960
|
if (found) {
|
|
919
|
-
displayColumn = found.COLUMN_NAME;
|
|
961
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
920
962
|
break;
|
|
921
963
|
}
|
|
922
964
|
}
|
|
923
965
|
if (!displayColumn) {
|
|
924
966
|
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
925
|
-
const found = refColumns.find(
|
|
926
|
-
|
|
927
|
-
|
|
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
|
+
});
|
|
928
972
|
if (found) {
|
|
929
|
-
displayColumn = found.COLUMN_NAME;
|
|
973
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
930
974
|
}
|
|
931
975
|
}
|
|
932
|
-
const
|
|
933
|
-
|
|
976
|
+
const quotedDisplayColumn = displayColumn ? dialect.quoteId(displayColumn) : null;
|
|
977
|
+
const selectCols = displayColumn ? `${quotedRefColumn}, ${quotedDisplayColumn}` : quotedRefColumn;
|
|
978
|
+
query = `SELECT DISTINCT ${selectCols} FROM ${quotedRefSchema}.${quotedRefTable}`;
|
|
934
979
|
if (searchQuery && searchQuery.trim()) {
|
|
935
980
|
if (displayColumn) {
|
|
936
|
-
query += ` WHERE
|
|
981
|
+
query += ` WHERE ${quotedDisplayColumn} LIKE ${dialect.param(1)} OR ${quotedRefColumn} LIKE ${dialect.param(1)}`;
|
|
937
982
|
} else {
|
|
938
|
-
query += ` WHERE
|
|
983
|
+
query += ` WHERE ${quotedRefColumn} LIKE ${dialect.param(1)}`;
|
|
939
984
|
}
|
|
940
|
-
params.push({ name: "search", value: `%${searchQuery.trim()}
|
|
941
|
-
query += ` AND
|
|
985
|
+
params.push({ name: "search", value: `%${searchQuery.trim()}%` });
|
|
986
|
+
query += ` AND ${quotedRefColumn} IS NOT NULL`;
|
|
942
987
|
} else {
|
|
943
|
-
query += ` WHERE
|
|
988
|
+
query += ` WHERE ${quotedRefColumn} IS NOT NULL`;
|
|
944
989
|
}
|
|
945
|
-
query += ` ORDER BY ${displayColumn ?
|
|
990
|
+
query += ` ORDER BY ${displayColumn ? quotedDisplayColumn : quotedRefColumn} ${dialect.limitOffset(0, 1e3)}`;
|
|
946
991
|
}
|
|
947
992
|
} else {
|
|
948
|
-
const dataType = columnInfo.DATA_TYPE.toLowerCase();
|
|
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);
|
|
949
997
|
const columnsToSelect = columnsParam ? columnsParam.split(",").map((c) => c.trim()).filter((c) => c) : [];
|
|
950
998
|
if (columnsToSelect.length > 0) {
|
|
951
|
-
const
|
|
952
|
-
const keyColumn =
|
|
953
|
-
const orderByColumn =
|
|
954
|
-
query = `SELECT DISTINCT ${
|
|
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}`;
|
|
955
1003
|
if (searchQuery && searchQuery.trim()) {
|
|
956
|
-
const searchCols =
|
|
957
|
-
query += ` WHERE (${searchCols}) AND
|
|
958
|
-
params.push({ name: "search", value: `%${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()}%` });
|
|
959
1007
|
} else {
|
|
960
|
-
query += ` WHERE
|
|
1008
|
+
query += ` WHERE ${keyColumn} IS NOT NULL`;
|
|
961
1009
|
}
|
|
962
|
-
query += ` ORDER BY
|
|
1010
|
+
query += ` ORDER BY ${orderByColumn} ${dialect.limitOffset(0, 1e3)}`;
|
|
963
1011
|
} else {
|
|
964
|
-
query = `SELECT DISTINCT
|
|
1012
|
+
query = `SELECT DISTINCT ${quotedColumn} FROM ${quotedSchema}.${quotedTable}`;
|
|
965
1013
|
if (searchQuery && searchQuery.trim()) {
|
|
966
1014
|
if (["varchar", "nvarchar", "char", "nchar", "text", "ntext"].some((t) => dataType.includes(t))) {
|
|
967
|
-
query += ` WHERE
|
|
968
|
-
params.push({ name: "search", value: `%${searchQuery.trim()}
|
|
969
|
-
query += ` AND
|
|
1015
|
+
query += ` WHERE ${quotedColumn} LIKE ${dialect.param(1)}`;
|
|
1016
|
+
params.push({ name: "search", value: `%${searchQuery.trim()}%` });
|
|
1017
|
+
query += ` AND ${quotedColumn} IS NOT NULL`;
|
|
970
1018
|
} else {
|
|
971
|
-
query += ` WHERE
|
|
1019
|
+
query += ` WHERE ${quotedColumn} IS NOT NULL`;
|
|
972
1020
|
}
|
|
973
1021
|
} else {
|
|
974
|
-
query += ` WHERE
|
|
1022
|
+
query += ` WHERE ${quotedColumn} IS NOT NULL`;
|
|
975
1023
|
}
|
|
976
|
-
query += ` ORDER BY
|
|
1024
|
+
query += ` ORDER BY ${quotedColumn} ${dialect.limitOffset(0, 1e3)}`;
|
|
977
1025
|
}
|
|
978
1026
|
}
|
|
979
1027
|
const result = await executeQuery(query, params);
|
|
@@ -981,8 +1029,8 @@ tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
|
981
1029
|
} catch (error) {
|
|
982
1030
|
console.error("Error fetching distinct values:", error);
|
|
983
1031
|
const errorMessage = error.message || "";
|
|
984
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
985
|
-
const { disconnect: disconnect2 } = await import("./
|
|
1032
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
1033
|
+
const { disconnect: disconnect2 } = await import("./db-CJPCGHL3.js");
|
|
986
1034
|
await disconnect2();
|
|
987
1035
|
}
|
|
988
1036
|
res.status(500).json({ error: error.message || "Failed to fetch distinct values" });
|
|
@@ -994,24 +1042,33 @@ import { Router as Router3 } from "express";
|
|
|
994
1042
|
var queryRoutes = Router3();
|
|
995
1043
|
queryRoutes.post("/", async (req, res) => {
|
|
996
1044
|
try {
|
|
997
|
-
const { query: sqlQuery } = req.body;
|
|
1045
|
+
const { query: sqlQuery, queryId } = req.body;
|
|
998
1046
|
if (!sqlQuery || typeof sqlQuery !== "string") {
|
|
999
1047
|
return res.status(400).json({ error: "Query is required" });
|
|
1000
1048
|
}
|
|
1049
|
+
const activeQueryId = queryId || generateQueryId();
|
|
1001
1050
|
const startTime = Date.now();
|
|
1002
|
-
const { recordsets: resultSets, columnMetadata } = await executeQueryMultiple(sqlQuery);
|
|
1051
|
+
const { recordsets: resultSets, columnMetadata } = await executeQueryMultiple(sqlQuery, void 0, activeQueryId);
|
|
1003
1052
|
const executionTime = Date.now() - startTime;
|
|
1004
1053
|
res.json({
|
|
1005
1054
|
data: resultSets[0] || [],
|
|
1006
1055
|
resultSets: resultSets.length > 0 ? resultSets : [],
|
|
1007
1056
|
executionTime,
|
|
1008
|
-
columnMetadata
|
|
1057
|
+
columnMetadata,
|
|
1009
1058
|
// Include column metadata for empty result sets
|
|
1059
|
+
queryId: activeQueryId
|
|
1060
|
+
// Return queryId so client can track it
|
|
1010
1061
|
});
|
|
1011
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
|
+
}
|
|
1012
1069
|
const errorMessage = error.message || "";
|
|
1013
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
1014
|
-
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");
|
|
1015
1072
|
await disconnect2();
|
|
1016
1073
|
}
|
|
1017
1074
|
res.status(500).json({
|
|
@@ -1020,6 +1077,24 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
1020
1077
|
});
|
|
1021
1078
|
}
|
|
1022
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
|
+
});
|
|
1023
1098
|
|
|
1024
1099
|
// src/server/index.ts
|
|
1025
1100
|
var __filename = fileURLToPath(import.meta.url);
|