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