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