datapeek 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/chunk-2I4MNNVK.js +628 -0
- package/dist/cli/chunk-4GDQ7VJZ.js +662 -0
- package/dist/cli/chunk-4MIQHH6K.js +47 -0
- package/dist/cli/chunk-6K3WSEIM.js +375 -0
- package/dist/cli/chunk-6PMCDR7J.js +628 -0
- package/dist/cli/chunk-B7K46745.js +605 -0
- package/dist/cli/chunk-CK2O76SG.js +713 -0
- package/dist/cli/chunk-FXOMCTFV.js +696 -0
- package/dist/cli/chunk-G4ID5SFT.js +628 -0
- package/dist/cli/chunk-GCVFEHSZ.js +708 -0
- package/dist/cli/chunk-LGE2JQ3S.js +628 -0
- package/dist/cli/chunk-NDLKLB5A.js +710 -0
- package/dist/cli/chunk-PG6I26XT.js +614 -0
- package/dist/cli/chunk-RK2Q2AX5.js +582 -0
- package/dist/cli/chunk-S5LRCCK6.js +582 -0
- package/dist/cli/chunk-UBFQXPPF.js +701 -0
- package/dist/cli/chunk-X2DQBOP6.js +712 -0
- package/dist/cli/chunk-XRMPVZIS.js +709 -0
- package/dist/cli/chunk-Z5G3UEXN.js +711 -0
- package/dist/cli/chunk-ZMP7S3G6.js +696 -0
- package/dist/cli/db-32TDDTFP.js +28 -0
- package/dist/cli/db-47O74DN5.js +34 -0
- package/dist/cli/db-4U22OWGB.js +29 -0
- package/dist/cli/db-CL72XDNO.js +34 -0
- package/dist/cli/db-CZCZOEID.js +28 -0
- package/dist/cli/db-IX3DXZ2C.js +34 -0
- package/dist/cli/db-LJCRTZTY.js +34 -0
- package/dist/cli/db-LNX4UMRF.js +28 -0
- package/dist/cli/db-NESFXDWL.js +28 -0
- package/dist/cli/db-OIJ2PJK6.js +34 -0
- package/dist/cli/db-OPAF6ZO3.js +28 -0
- package/dist/cli/db-PQURQHSK.js +28 -0
- package/dist/cli/db-QN5MTDYB.js +28 -0
- package/dist/cli/db-RBMUR3OK.js +34 -0
- package/dist/cli/db-REMUYLPP.js +34 -0
- package/dist/cli/db-RTHQ4ZAU.js +28 -0
- package/dist/cli/db-SHFLGGWD.js +28 -0
- package/dist/cli/db-ULMGZXQ5.js +34 -0
- package/dist/cli/db-YOGJ4LXE.js +28 -0
- package/dist/cli/dev.js +717 -291
- package/dist/cli/index.js +720 -293
- package/dist/cli/queryCancellation-4CMKYTFU.js +46 -0
- package/dist/cli/queryCancellation-E5O4FBUY.js +14 -0
- package/dist/client/assets/index-BXssAVuE.css +1 -0
- package/dist/client/assets/index-DgwSpfvt.js +378 -0
- package/dist/client/index.html +2 -2
- package/dist/server/chunk-26GSUAI4.js +710 -0
- package/dist/server/chunk-36OOXHKI.js +659 -0
- package/dist/server/chunk-3DAIYH42.js +44 -0
- package/dist/server/chunk-4ELMZYWA.js +611 -0
- package/dist/server/chunk-5ESS5YBD.js +707 -0
- package/dist/server/chunk-7HIBWXBL.js +625 -0
- package/dist/server/chunk-BH22Q2FV.js +705 -0
- package/dist/server/chunk-CFU2CLKS.js +693 -0
- package/dist/server/chunk-CXA3HXTK.js +698 -0
- package/dist/server/chunk-DAUUXYQF.js +706 -0
- package/dist/server/chunk-FMSIEQ2T.js +579 -0
- package/dist/server/chunk-G6A463RM.js +625 -0
- package/dist/server/chunk-H6DUOXOK.js +625 -0
- package/dist/server/chunk-JCRZ6H7P.js +579 -0
- package/dist/server/chunk-L6LL3NE7.js +602 -0
- package/dist/server/chunk-TH6TUILG.js +709 -0
- package/dist/server/chunk-UJ2LRK25.js +373 -0
- package/dist/server/chunk-YI67PW64.js +625 -0
- package/dist/server/chunk-ZDZ2C5ZQ.js +693 -0
- package/dist/server/chunk-ZJOVVCPT.js +708 -0
- package/dist/server/db-22DQ6YG7.js +32 -0
- package/dist/server/db-3CBO6RHU.js +26 -0
- package/dist/server/db-3XN2KWYT.js +32 -0
- package/dist/server/db-3Y6K4EDP.js +26 -0
- package/dist/server/db-57HXNWPD.js +26 -0
- package/dist/server/db-5GCTDKYH.js +27 -0
- package/dist/server/db-5VQOTJ52.js +26 -0
- package/dist/server/db-B5L7VQ7E.js +32 -0
- package/dist/server/db-CJPCGHL3.js +32 -0
- package/dist/server/db-DAQ4BVCU.js +32 -0
- package/dist/server/db-EEA6EWHL.js +26 -0
- package/dist/server/db-KR7Z5M5V.js +32 -0
- package/dist/server/db-LMSBISDM.js +26 -0
- package/dist/server/db-LSOQE7N7.js +32 -0
- package/dist/server/db-NHBP6YLH.js +26 -0
- package/dist/server/db-OWVJVBBD.js +26 -0
- package/dist/server/db-TTVXKX7P.js +26 -0
- package/dist/server/db-YJ753EQV.js +26 -0
- package/dist/server/db-ZV6GZHFU.js +32 -0
- package/dist/server/dev.js +717 -291
- package/dist/server/index.js +717 -291
- package/dist/server/queryCancellation-M56DISJX.js +43 -0
- package/dist/server/queryCancellation-T2HSNMIV.js +12 -0
- package/package.json +3 -1
- package/dist/client/assets/index-BhZ7NrhL.css +0 -1
- package/dist/client/assets/index-D-mash8_.js +0 -370
package/dist/cli/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) {
|
|
146
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
147
|
+
}
|
|
148
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
149
|
+
if (!isConnected) {
|
|
125
150
|
return res.status(400).json({ error: "Not connected to database" });
|
|
126
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) {
|
|
179
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
180
|
+
}
|
|
181
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
182
|
+
if (!isConnected) {
|
|
151
183
|
return res.status(400).json({ error: "Not connected to database" });
|
|
152
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" });
|
|
@@ -219,62 +252,230 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
219
252
|
const sortDirection = req.query.sortDirection || "asc";
|
|
220
253
|
const fkDisplayMode = req.query.fkDisplayMode || "key-only";
|
|
221
254
|
const offset = (page - 1) * pageSize;
|
|
222
|
-
const
|
|
255
|
+
const parsedFilters = [];
|
|
256
|
+
const seenFilterColumns = /* @__PURE__ */ new Set();
|
|
257
|
+
const parseFilterEntry = (columnName, rawValue) => {
|
|
258
|
+
if (!columnName || rawValue === null || rawValue === void 0 || seenFilterColumns.has(columnName)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const filterValue = String(rawValue).trim();
|
|
262
|
+
if (!filterValue) return;
|
|
263
|
+
try {
|
|
264
|
+
const parsed = JSON.parse(filterValue);
|
|
265
|
+
if (parsed && typeof parsed === "object" && parsed.operator) {
|
|
266
|
+
parsedFilters.push({
|
|
267
|
+
column: columnName,
|
|
268
|
+
operator: parsed.operator,
|
|
269
|
+
value: parsed.value,
|
|
270
|
+
dataType: parsed.dataType
|
|
271
|
+
});
|
|
272
|
+
} else {
|
|
273
|
+
parsedFilters.push({
|
|
274
|
+
column: columnName,
|
|
275
|
+
operator: "contains",
|
|
276
|
+
value: filterValue
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
parsedFilters.push({
|
|
281
|
+
column: columnName,
|
|
282
|
+
operator: "contains",
|
|
283
|
+
value: filterValue
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
seenFilterColumns.add(columnName);
|
|
287
|
+
};
|
|
223
288
|
Object.keys(req.query).forEach((key) => {
|
|
224
289
|
const match = key.match(/^filter\[(.+)\]$/);
|
|
225
|
-
if (match
|
|
226
|
-
|
|
290
|
+
if (match) {
|
|
291
|
+
const columnName = match[1];
|
|
292
|
+
parseFilterEntry(columnName, req.query[key]);
|
|
227
293
|
}
|
|
228
294
|
});
|
|
229
|
-
|
|
230
|
-
|
|
295
|
+
const nestedFilters = req.query.filter;
|
|
296
|
+
if (nestedFilters && typeof nestedFilters === "object" && !Array.isArray(nestedFilters)) {
|
|
297
|
+
Object.entries(nestedFilters).forEach(([columnName, rawValue]) => {
|
|
298
|
+
parseFilterEntry(columnName, rawValue);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
console.log("Parsed filters:", parsedFilters);
|
|
231
302
|
const pool = getConnection();
|
|
232
|
-
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) {
|
|
233
308
|
return res.status(400).json({ error: "Not connected to database" });
|
|
234
309
|
}
|
|
235
|
-
const
|
|
236
|
-
|
|
310
|
+
const dialect = getDialect();
|
|
311
|
+
const columnMetadata = {};
|
|
312
|
+
if (parsedFilters.length > 0) {
|
|
237
313
|
try {
|
|
238
|
-
const columnNames =
|
|
239
|
-
const placeholders = columnNames.map((_, i) =>
|
|
314
|
+
const columnNames = [...new Set(parsedFilters.map((f) => f.column))];
|
|
315
|
+
const placeholders = columnNames.map((_, i) => dialect.param(i + 3)).join(", ");
|
|
240
316
|
const validateQuery = `
|
|
241
|
-
SELECT
|
|
242
|
-
FROM
|
|
243
|
-
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})
|
|
244
320
|
`;
|
|
245
321
|
const validateParams = [
|
|
246
|
-
{ name: "schema", value: schema
|
|
247
|
-
{ name: "table", value: table
|
|
248
|
-
...columnNames.map((col
|
|
322
|
+
{ name: "schema", value: schema },
|
|
323
|
+
{ name: "table", value: table },
|
|
324
|
+
...columnNames.map((col) => ({ name: "col", value: col }))
|
|
249
325
|
];
|
|
250
326
|
const validateResult = await executeQuery(validateQuery, validateParams);
|
|
251
|
-
|
|
252
|
-
|
|
327
|
+
validateResult.forEach((r) => {
|
|
328
|
+
const colName = r.column_name || r.COLUMN_NAME;
|
|
329
|
+
const dataType = r.data_type || r.DATA_TYPE;
|
|
330
|
+
columnMetadata[colName] = {
|
|
331
|
+
dataType,
|
|
332
|
+
exists: true
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
console.log("Column metadata:", columnMetadata);
|
|
253
336
|
} catch (e) {
|
|
254
337
|
console.error("Error validating filter columns:", e);
|
|
255
|
-
console.error("Filters that failed validation:", filters);
|
|
256
338
|
}
|
|
257
339
|
}
|
|
258
340
|
let whereClause = "";
|
|
259
341
|
const filterParams = [];
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
342
|
+
let paramIndex = 1;
|
|
343
|
+
if (parsedFilters.length > 0) {
|
|
344
|
+
const whereConditions = [];
|
|
345
|
+
parsedFilters.forEach((filter) => {
|
|
346
|
+
const columnName = filter.column;
|
|
347
|
+
const metadata = columnMetadata[columnName];
|
|
348
|
+
if (!metadata || !metadata.exists) {
|
|
349
|
+
console.warn(`Filter column ${columnName} not found, skipping`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const dataType = filter.dataType || metadata.dataType;
|
|
353
|
+
const operator = filter.operator;
|
|
354
|
+
const value = filter.value;
|
|
355
|
+
if (value === null || value === void 0 || value === "") {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const quotedColumn = dialect.quoteId(columnName);
|
|
359
|
+
try {
|
|
360
|
+
let condition = "";
|
|
361
|
+
switch (operator) {
|
|
362
|
+
// Text operators
|
|
363
|
+
case "contains":
|
|
364
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}%` });
|
|
365
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
366
|
+
break;
|
|
367
|
+
case "equals":
|
|
368
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
369
|
+
condition = `${quotedColumn} = ${dialect.param(paramIndex)}`;
|
|
370
|
+
break;
|
|
371
|
+
case "startsWith":
|
|
372
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `${String(value)}%` });
|
|
373
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
374
|
+
break;
|
|
375
|
+
case "endsWith":
|
|
376
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}` });
|
|
377
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
378
|
+
break;
|
|
379
|
+
case "notContains":
|
|
380
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}%` });
|
|
381
|
+
condition = `${quotedColumn} NOT LIKE ${dialect.param(paramIndex)}`;
|
|
382
|
+
break;
|
|
383
|
+
// Number operators
|
|
384
|
+
case "eq":
|
|
385
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
386
|
+
condition = `${quotedColumn} = ${dialect.param(paramIndex)}`;
|
|
387
|
+
break;
|
|
388
|
+
case "gt":
|
|
389
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
390
|
+
condition = `${quotedColumn} > ${dialect.param(paramIndex)}`;
|
|
391
|
+
break;
|
|
392
|
+
case "gte":
|
|
393
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
394
|
+
condition = `${quotedColumn} >= ${dialect.param(paramIndex)}`;
|
|
395
|
+
break;
|
|
396
|
+
case "lt":
|
|
397
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
398
|
+
condition = `${quotedColumn} < ${dialect.param(paramIndex)}`;
|
|
399
|
+
break;
|
|
400
|
+
case "lte":
|
|
401
|
+
filterParams.push({ name: `filter${paramIndex}`, value: Number(value) });
|
|
402
|
+
condition = `${quotedColumn} <= ${dialect.param(paramIndex)}`;
|
|
403
|
+
break;
|
|
404
|
+
case "between":
|
|
405
|
+
if (typeof value === "object" && "from" in value && "to" in value) {
|
|
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)}`;
|
|
411
|
+
paramIndex++;
|
|
412
|
+
}
|
|
413
|
+
break;
|
|
414
|
+
// Date operators
|
|
415
|
+
case "dateEq":
|
|
416
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
417
|
+
condition = `${dialect.castToDate(quotedColumn)} = ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
418
|
+
break;
|
|
419
|
+
case "dateAfter":
|
|
420
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
421
|
+
condition = `${dialect.castToDate(quotedColumn)} > ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
422
|
+
break;
|
|
423
|
+
case "dateBefore":
|
|
424
|
+
filterParams.push({ name: `filter${paramIndex}`, value: String(value) });
|
|
425
|
+
condition = `${dialect.castToDate(quotedColumn)} < ${dialect.castToDate(dialect.param(paramIndex))}`;
|
|
426
|
+
break;
|
|
427
|
+
case "dateBetween":
|
|
428
|
+
if (typeof value === "object" && "from" in value && "to" in value) {
|
|
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))}`;
|
|
434
|
+
paramIndex++;
|
|
435
|
+
}
|
|
436
|
+
break;
|
|
437
|
+
// Multiple select operators
|
|
438
|
+
case "in":
|
|
439
|
+
case "notIn":
|
|
440
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
441
|
+
const placeholders = [];
|
|
442
|
+
value.forEach((val, i) => {
|
|
443
|
+
const currentIndex = paramIndex + i;
|
|
444
|
+
filterParams.push({ name: `filter${currentIndex}`, value: val });
|
|
445
|
+
placeholders.push(dialect.param(currentIndex));
|
|
446
|
+
});
|
|
447
|
+
const inOperator = operator === "in" ? "IN" : "NOT IN";
|
|
448
|
+
condition = `${quotedColumn} ${inOperator} (${placeholders.join(", ")})`;
|
|
449
|
+
paramIndex += value.length - 1;
|
|
450
|
+
}
|
|
451
|
+
break;
|
|
452
|
+
default:
|
|
453
|
+
console.warn(`Unknown filter operator: ${operator}, falling back to contains`);
|
|
454
|
+
filterParams.push({ name: `filter${paramIndex}`, value: `%${String(value)}%` });
|
|
455
|
+
condition = `${quotedColumn} LIKE ${dialect.param(paramIndex)}`;
|
|
456
|
+
}
|
|
457
|
+
if (condition) {
|
|
458
|
+
whereConditions.push(condition);
|
|
459
|
+
paramIndex++;
|
|
460
|
+
}
|
|
461
|
+
} catch (error) {
|
|
462
|
+
console.error(`Error building filter condition for ${columnName}:`, error);
|
|
463
|
+
}
|
|
264
464
|
});
|
|
265
|
-
if (
|
|
266
|
-
const whereConditions = validFilters.map((col, index) => {
|
|
267
|
-
const filterValue = filters[col];
|
|
268
|
-
filterParams.push({ name: `filter${index}`, value: `%${filterValue.trim()}%`, type: sql.NVarChar });
|
|
269
|
-
return `[${col}] LIKE @filter${index}`;
|
|
270
|
-
});
|
|
465
|
+
if (whereConditions.length > 0) {
|
|
271
466
|
whereClause = `WHERE ${whereConditions.join(" AND ")}`;
|
|
272
467
|
console.log("Applying WHERE clause:", whereClause);
|
|
273
|
-
console.log("Filter params:", filterParams);
|
|
274
|
-
console.log("Valid filters:", validFilters);
|
|
468
|
+
console.log("Filter params:", filterParams.map((p) => ({ name: p.name, value: p.value })));
|
|
275
469
|
}
|
|
276
470
|
}
|
|
277
|
-
const
|
|
471
|
+
const qualifiedDataWhereClause = parsedFilters.reduce((clause, filter) => {
|
|
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);
|
|
475
|
+
}, whereClause);
|
|
476
|
+
const quotedSchema = dialect.quoteId(schema);
|
|
477
|
+
const quotedTable = dialect.quoteId(table);
|
|
478
|
+
const countQuery = `SELECT COUNT(*) as total FROM ${quotedSchema}.${quotedTable}${whereClause ? " " + whereClause : ""}`;
|
|
278
479
|
const countResult = await executeQuery(countQuery, filterParams.length > 0 ? filterParams : []);
|
|
279
480
|
const total = countResult[0]?.total || 0;
|
|
280
481
|
let orderByColumn = sortColumn || "";
|
|
@@ -282,17 +483,19 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
282
483
|
if (orderByColumn) {
|
|
283
484
|
try {
|
|
284
485
|
const validateQuery = `
|
|
285
|
-
SELECT
|
|
286
|
-
FROM
|
|
287
|
-
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)}
|
|
288
489
|
`;
|
|
289
490
|
const validateResult = await executeQuery(validateQuery, [
|
|
290
|
-
{ name: "schema", value: schema
|
|
291
|
-
{ name: "table", value: table
|
|
292
|
-
{ name: "column", value: orderByColumn
|
|
491
|
+
{ name: "schema", value: schema },
|
|
492
|
+
{ name: "table", value: table },
|
|
493
|
+
{ name: "column", value: orderByColumn }
|
|
293
494
|
]);
|
|
294
495
|
if (validateResult.length === 0) {
|
|
295
496
|
orderByColumn = "";
|
|
497
|
+
} else {
|
|
498
|
+
orderByColumn = validateResult[0].column_name || validateResult[0].COLUMN_NAME || orderByColumn;
|
|
296
499
|
}
|
|
297
500
|
} catch (e) {
|
|
298
501
|
orderByColumn = "";
|
|
@@ -300,18 +503,20 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
300
503
|
}
|
|
301
504
|
if (!orderByColumn) {
|
|
302
505
|
try {
|
|
506
|
+
const topClause = dialect.topN(1);
|
|
303
507
|
const structureQuery = `
|
|
304
|
-
SELECT
|
|
305
|
-
FROM
|
|
306
|
-
WHERE
|
|
307
|
-
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) : ""}
|
|
308
513
|
`;
|
|
309
514
|
const structureResult = await executeQuery(structureQuery, [
|
|
310
|
-
{ name: "schema", value: schema
|
|
311
|
-
{ name: "table", value: table
|
|
515
|
+
{ name: "schema", value: schema },
|
|
516
|
+
{ name: "table", value: table }
|
|
312
517
|
]);
|
|
313
518
|
if (structureResult.length > 0) {
|
|
314
|
-
orderByColumn = structureResult[0].COLUMN_NAME;
|
|
519
|
+
orderByColumn = structureResult[0].column_name || structureResult[0].COLUMN_NAME;
|
|
315
520
|
}
|
|
316
521
|
} catch (e) {
|
|
317
522
|
}
|
|
@@ -319,93 +524,97 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
319
524
|
const fkJoins = [];
|
|
320
525
|
const fkSelects = [];
|
|
321
526
|
const fkDisplayColumns = {};
|
|
322
|
-
|
|
323
|
-
const fkQuery = `
|
|
527
|
+
const fkQuery = `
|
|
324
528
|
SELECT
|
|
325
|
-
kcu1.
|
|
326
|
-
kcu2.
|
|
327
|
-
kcu2.
|
|
328
|
-
kcu2.
|
|
329
|
-
FROM
|
|
330
|
-
INNER JOIN
|
|
331
|
-
ON rc.
|
|
332
|
-
AND rc.
|
|
333
|
-
AND rc.
|
|
334
|
-
INNER JOIN
|
|
335
|
-
ON rc.
|
|
336
|
-
AND rc.
|
|
337
|
-
AND rc.
|
|
338
|
-
AND kcu1.
|
|
339
|
-
WHERE kcu1.
|
|
340
|
-
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)}
|
|
341
545
|
`;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
SELECT
|
|
358
|
-
FROM
|
|
546
|
+
console.log("Fetching foreign keys with query:", fkQuery);
|
|
547
|
+
const foreignKeys = await executeQuery(fkQuery, [
|
|
548
|
+
{ name: "schema", value: schema },
|
|
549
|
+
{ name: "table", value: table }
|
|
550
|
+
]);
|
|
551
|
+
console.log(`Found ${foreignKeys.length} foreign key(s)`);
|
|
552
|
+
if (foreignKeys.length > 0) {
|
|
553
|
+
const uniqueRefTables = Array.from(
|
|
554
|
+
new Set(foreignKeys.map((fk) => `${fk.referencedSchema || fk.referenced_schema}.${fk.referencedTable || fk.referenced_table}`))
|
|
555
|
+
);
|
|
556
|
+
const tableConditions = uniqueRefTables.map((tableRef, idx) => {
|
|
557
|
+
const [refSchema, refTable] = tableRef.split(".");
|
|
558
|
+
return `(table_schema = ${dialect.param(idx * 2 + 1)} AND table_name = ${dialect.param(idx * 2 + 2)})`;
|
|
559
|
+
}).join(" OR ");
|
|
560
|
+
const batchColumnsQuery = `
|
|
561
|
+
SELECT table_schema, table_name, column_name, data_type, ordinal_position
|
|
562
|
+
FROM information_schema.columns
|
|
359
563
|
WHERE ${tableConditions}
|
|
360
|
-
ORDER BY
|
|
564
|
+
ORDER BY table_schema, table_name, ordinal_position
|
|
361
565
|
`;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
columnsByTable[key]
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
566
|
+
const batchParams = uniqueRefTables.flatMap((tableRef) => {
|
|
567
|
+
const [refSchema, refTable] = tableRef.split(".");
|
|
568
|
+
return [
|
|
569
|
+
{ name: "refSchema", value: refSchema },
|
|
570
|
+
{ name: "refTable", value: refTable }
|
|
571
|
+
];
|
|
572
|
+
});
|
|
573
|
+
console.log("Fetching referenced table columns with query:", batchColumnsQuery);
|
|
574
|
+
console.log("Batch parameters:", batchParams.map((p) => ({ name: p.name, value: p.value })));
|
|
575
|
+
const allRefColumns = await executeQuery(batchColumnsQuery, batchParams);
|
|
576
|
+
console.log(`Found columns for ${uniqueRefTables.length} referenced table(s)`);
|
|
577
|
+
const columnsByTable = {};
|
|
578
|
+
allRefColumns.forEach((col) => {
|
|
579
|
+
const schemaName = col.table_schema || col.TABLE_SCHEMA;
|
|
580
|
+
const tableName = col.table_name || col.TABLE_NAME;
|
|
581
|
+
const key = `${schemaName}.${tableName}`;
|
|
582
|
+
if (!columnsByTable[key]) {
|
|
583
|
+
columnsByTable[key] = [];
|
|
584
|
+
}
|
|
585
|
+
columnsByTable[key].push(col);
|
|
586
|
+
});
|
|
587
|
+
for (const fk of foreignKeys) {
|
|
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;
|
|
592
|
+
const tableKey = `${refSchema}.${refTable}`;
|
|
593
|
+
const refColumns = columnsByTable[tableKey] || [];
|
|
594
|
+
const preferredNames = ["name", "title", "description", "code"];
|
|
595
|
+
let displayColumn = null;
|
|
596
|
+
for (const preferredName of preferredNames) {
|
|
597
|
+
const found = refColumns.find((col) => {
|
|
598
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
599
|
+
return colName.toLowerCase() === preferredName.toLowerCase();
|
|
600
|
+
});
|
|
601
|
+
if (found) {
|
|
602
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
603
|
+
break;
|
|
398
604
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
605
|
+
}
|
|
606
|
+
if (!displayColumn) {
|
|
607
|
+
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
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
|
+
});
|
|
612
|
+
if (found) {
|
|
613
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
407
614
|
}
|
|
408
|
-
|
|
615
|
+
}
|
|
616
|
+
if (displayColumn) {
|
|
617
|
+
if (fkDisplayMode === "key-display" || fkDisplayMode === "display-only") {
|
|
409
618
|
const alias = `fk_${fkColumn}`;
|
|
410
619
|
fkJoins.push({
|
|
411
620
|
alias,
|
|
@@ -415,89 +624,79 @@ tableRoutes.get("/:schema/:table/data", async (req, res) => {
|
|
|
415
624
|
refColumn,
|
|
416
625
|
displayColumn
|
|
417
626
|
});
|
|
418
|
-
fkSelects.push(`${alias}.
|
|
419
|
-
fkDisplayColumns[fkColumn] = displayColumn;
|
|
627
|
+
fkSelects.push(`${dialect.quoteId(alias)}.${dialect.quoteId(displayColumn)} as ${dialect.quoteId(`${fkColumn}_display`)}`);
|
|
420
628
|
}
|
|
629
|
+
fkDisplayColumns[fkColumn] = displayColumn;
|
|
421
630
|
}
|
|
422
631
|
}
|
|
423
632
|
}
|
|
424
633
|
const baseTableAlias = "t";
|
|
425
|
-
|
|
634
|
+
const quotedTableAlias = dialect.quoteId(baseTableAlias);
|
|
635
|
+
let baseSelects = `${quotedTableAlias}.*`;
|
|
426
636
|
if (fkDisplayMode === "display-only") {
|
|
427
637
|
const fkColumnNames = fkJoins.map((fk) => fk.fkColumn);
|
|
428
638
|
if (fkColumnNames.length > 0) {
|
|
429
|
-
baseSelects =
|
|
639
|
+
baseSelects = `${quotedTableAlias}.*`;
|
|
430
640
|
}
|
|
431
641
|
}
|
|
432
642
|
const allSelects = `${baseSelects}${fkSelects.length > 0 ? ", " + fkSelects.join(", ") : ""}`;
|
|
433
643
|
const buildJoinClauses = (tableAlias) => {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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 ");
|
|
437
653
|
};
|
|
438
654
|
let data;
|
|
439
655
|
let generatedQuery = "";
|
|
656
|
+
const offsetParamIndex = filterParams.length + 1;
|
|
657
|
+
const pageSizeParamIndex = filterParams.length + 2;
|
|
440
658
|
if (orderByColumn) {
|
|
659
|
+
const quotedOrderByColumn = dialect.quoteId(orderByColumn);
|
|
660
|
+
const limitOffsetClause = dialect.limitOffset(offset, pageSize);
|
|
441
661
|
const dataQuery = `
|
|
442
662
|
SELECT ${allSelects}
|
|
443
|
-
FROM
|
|
663
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}
|
|
444
664
|
${buildJoinClauses(baseTableAlias)}
|
|
445
|
-
${
|
|
446
|
-
ORDER BY ${
|
|
447
|
-
|
|
448
|
-
FETCH NEXT @pageSize ROWS ONLY
|
|
665
|
+
${qualifiedDataWhereClause}
|
|
666
|
+
ORDER BY ${quotedTableAlias}.${quotedOrderByColumn} ${orderByDirection}
|
|
667
|
+
${limitOffsetClause}
|
|
449
668
|
`;
|
|
450
669
|
generatedQuery = `SELECT ${allSelects}
|
|
451
|
-
FROM
|
|
452
|
-
ORDER BY ${
|
|
453
|
-
|
|
454
|
-
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}`;
|
|
455
673
|
console.log("Executing SQL query:", dataQuery);
|
|
456
674
|
console.log("Query parameters:", {
|
|
457
675
|
offset,
|
|
458
676
|
pageSize,
|
|
459
677
|
filterParams: filterParams.map((p) => ({ name: p.name, value: p.value }))
|
|
460
678
|
});
|
|
461
|
-
data = await executeQuery(dataQuery,
|
|
462
|
-
{ name: "offset", value: offset, type: sql.Int },
|
|
463
|
-
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
464
|
-
...filterParams
|
|
465
|
-
]);
|
|
679
|
+
data = await executeQuery(dataQuery, filterParams);
|
|
466
680
|
} else {
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
FROM [${schema}].[${table}]
|
|
470
|
-
${whereClause}
|
|
471
|
-
`;
|
|
681
|
+
const dbType = getDbType();
|
|
682
|
+
const limitOffsetClause = dialect.limitOffset(offset, pageSize);
|
|
472
683
|
const dataQuery = `
|
|
473
684
|
SELECT ${allSelects}
|
|
474
|
-
FROM
|
|
685
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}
|
|
475
686
|
${buildJoinClauses(baseTableAlias)}
|
|
476
|
-
|
|
477
|
-
|
|
687
|
+
${qualifiedDataWhereClause}
|
|
688
|
+
${limitOffsetClause}
|
|
478
689
|
`;
|
|
479
690
|
generatedQuery = `SELECT ${allSelects}
|
|
480
|
-
FROM (
|
|
481
|
-
|
|
482
|
-
FROM [${schema}].[${table}]${whereClause ? "\n " + whereClause : ""}
|
|
483
|
-
) ${baseTableAlias}${fkJoins.length > 0 ? "\n" + buildJoinClauses(baseTableAlias) : ""}
|
|
484
|
-
WHERE ${baseTableAlias}.rn > ${offset} AND ${baseTableAlias}.rn <= ${offset + pageSize}
|
|
485
|
-
ORDER BY ${baseTableAlias}.rn`;
|
|
691
|
+
FROM ${quotedSchema}.${quotedTable} ${quotedTableAlias}${fkJoins.length > 0 ? "\n" + buildJoinClauses(baseTableAlias) : ""}${qualifiedDataWhereClause ? "\n" + qualifiedDataWhereClause : ""}
|
|
692
|
+
${limitOffsetClause}`;
|
|
486
693
|
console.log("Executing SQL query:", dataQuery);
|
|
487
694
|
console.log("Query parameters:", {
|
|
488
695
|
offset,
|
|
489
696
|
pageSize,
|
|
490
697
|
filterParams: filterParams.map((p) => ({ name: p.name, value: p.value }))
|
|
491
698
|
});
|
|
492
|
-
data = await executeQuery(dataQuery,
|
|
493
|
-
{ name: "offset", value: offset, type: sql.Int },
|
|
494
|
-
{ name: "pageSize", value: pageSize, type: sql.Int },
|
|
495
|
-
...filterParams
|
|
496
|
-
]);
|
|
497
|
-
data = data.map((row) => {
|
|
498
|
-
const { rn, ...rest } = row;
|
|
499
|
-
return rest;
|
|
500
|
-
});
|
|
699
|
+
data = await executeQuery(dataQuery, filterParams);
|
|
501
700
|
}
|
|
502
701
|
if (fkDisplayMode === "display-only") {
|
|
503
702
|
const fkColumnNames = fkJoins.map((fk) => fk.fkColumn);
|
|
@@ -514,6 +713,34 @@ ORDER BY ${baseTableAlias}.rn`;
|
|
|
514
713
|
return filteredRow;
|
|
515
714
|
});
|
|
516
715
|
}
|
|
716
|
+
if (generatedQuery && filterParams.length > 0) {
|
|
717
|
+
const formatSqlLiteral = (value) => {
|
|
718
|
+
if (value === null || value === void 0) return "NULL";
|
|
719
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
|
|
720
|
+
if (typeof value === "boolean") return value ? "1" : "0";
|
|
721
|
+
if (value instanceof Date) return `'${value.toISOString().replace("T", " ").slice(0, 19)}'`;
|
|
722
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
723
|
+
};
|
|
724
|
+
const paramValues = new Map(
|
|
725
|
+
filterParams.map((p) => [p.name, p.value])
|
|
726
|
+
);
|
|
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
|
+
}
|
|
743
|
+
}
|
|
517
744
|
res.json({
|
|
518
745
|
data,
|
|
519
746
|
query: generatedQuery,
|
|
@@ -529,8 +756,8 @@ ORDER BY ${baseTableAlias}.rn`;
|
|
|
529
756
|
} catch (error) {
|
|
530
757
|
console.error("Error fetching table data:", error);
|
|
531
758
|
const errorMessage = error.message || "";
|
|
532
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
533
|
-
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");
|
|
534
761
|
await disconnect2();
|
|
535
762
|
}
|
|
536
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";
|
|
@@ -557,73 +784,68 @@ tableRoutes.post("/:schema/:table/related-data", async (req, res) => {
|
|
|
557
784
|
return res.status(400).json({ error: "Missing required parameters" });
|
|
558
785
|
}
|
|
559
786
|
const pool = getConnection();
|
|
560
|
-
if (!pool
|
|
787
|
+
if (!pool) {
|
|
788
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
789
|
+
}
|
|
790
|
+
const isConnected = "connected" in pool ? pool.connected === true : !pool.ended;
|
|
791
|
+
if (!isConnected) {
|
|
561
792
|
return res.status(400).json({ error: "Not connected to database" });
|
|
562
793
|
}
|
|
794
|
+
const dialect = getDialect();
|
|
563
795
|
const columnsQuery = `
|
|
564
|
-
SELECT
|
|
565
|
-
FROM
|
|
566
|
-
WHERE
|
|
567
|
-
AND
|
|
568
|
-
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
|
|
569
801
|
`;
|
|
570
802
|
const columns = await executeQuery(columnsQuery, [
|
|
571
|
-
{ name: "refSchema", value: referencedSchema
|
|
572
|
-
{ name: "refTable", value: referencedTable
|
|
803
|
+
{ name: "refSchema", value: referencedSchema },
|
|
804
|
+
{ name: "refTable", value: referencedTable }
|
|
573
805
|
]);
|
|
574
|
-
const referencedColInfo = columns.find(
|
|
575
|
-
|
|
576
|
-
|
|
806
|
+
const referencedColInfo = columns.find((col) => {
|
|
807
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
808
|
+
return colName === referencedColumn;
|
|
809
|
+
});
|
|
577
810
|
if (!referencedColInfo) {
|
|
578
811
|
return res.status(400).json({ error: `Referenced column '${referencedColumn}' not found` });
|
|
579
812
|
}
|
|
580
|
-
const getSqlType = (dataType) => {
|
|
581
|
-
const dt = dataType.toLowerCase();
|
|
582
|
-
if (dt === "int" || dt === "integer") return sql.Int;
|
|
583
|
-
if (dt === "bigint") return sql.BigInt;
|
|
584
|
-
if (dt === "smallint") return sql.SmallInt;
|
|
585
|
-
if (dt === "tinyint") return sql.TinyInt;
|
|
586
|
-
if (dt === "bit") return sql.Bit;
|
|
587
|
-
if (dt === "float" || dt === "real" || dt === "double precision") return sql.Float;
|
|
588
|
-
if (dt === "decimal" || dt === "numeric" || dt === "money" || dt === "smallmoney") return sql.Decimal(18, 0);
|
|
589
|
-
if (dt === "datetime" || dt === "datetime2" || dt === "smalldatetime") return sql.DateTime;
|
|
590
|
-
if (dt === "date") return sql.Date;
|
|
591
|
-
if (dt === "time") return sql.Time;
|
|
592
|
-
if (dt === "uniqueidentifier") return sql.UniqueIdentifier;
|
|
593
|
-
return sql.NVarChar;
|
|
594
|
-
};
|
|
595
|
-
const referencedColumnType = getSqlType(referencedColInfo.DATA_TYPE);
|
|
596
813
|
const preferredNames = ["name", "title", "description", "code"];
|
|
597
814
|
let displayColumn = null;
|
|
598
815
|
for (const preferredName of preferredNames) {
|
|
599
|
-
const found = columns.find(
|
|
600
|
-
|
|
601
|
-
|
|
816
|
+
const found = columns.find((col) => {
|
|
817
|
+
const colName = col.column_name || col.COLUMN_NAME;
|
|
818
|
+
return colName.toLowerCase() === preferredName.toLowerCase();
|
|
819
|
+
});
|
|
602
820
|
if (found) {
|
|
603
|
-
displayColumn = found.COLUMN_NAME;
|
|
821
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
604
822
|
break;
|
|
605
823
|
}
|
|
606
824
|
}
|
|
607
825
|
if (!displayColumn) {
|
|
608
826
|
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
609
|
-
const found = columns.find(
|
|
610
|
-
|
|
611
|
-
|
|
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
|
+
});
|
|
612
831
|
if (found) {
|
|
613
|
-
displayColumn = found.COLUMN_NAME;
|
|
832
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
614
833
|
}
|
|
615
834
|
}
|
|
616
|
-
const
|
|
617
|
-
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;
|
|
618
841
|
const dataQuery = `
|
|
619
842
|
SELECT ${selectColumns}
|
|
620
|
-
FROM
|
|
621
|
-
WHERE
|
|
843
|
+
FROM ${quotedRefSchema}.${quotedRefTable}
|
|
844
|
+
WHERE ${quotedRefColumn} IN (${placeholders})
|
|
622
845
|
`;
|
|
623
|
-
const params = ids.map((id
|
|
624
|
-
name:
|
|
625
|
-
value: id
|
|
626
|
-
type: referencedColumnType
|
|
846
|
+
const params = ids.map((id) => ({
|
|
847
|
+
name: "id",
|
|
848
|
+
value: id
|
|
627
849
|
}));
|
|
628
850
|
const result = await executeQuery(dataQuery, params);
|
|
629
851
|
const dataMap = {};
|
|
@@ -635,37 +857,223 @@ tableRoutes.post("/:schema/:table/related-data", async (req, res) => {
|
|
|
635
857
|
} catch (error) {
|
|
636
858
|
console.error("Error fetching related data:", error);
|
|
637
859
|
const errorMessage = error.message || "";
|
|
638
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
639
|
-
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");
|
|
640
862
|
await disconnect2();
|
|
641
863
|
}
|
|
642
864
|
res.status(500).json({ error: error.message || "Failed to fetch related data" });
|
|
643
865
|
}
|
|
644
866
|
});
|
|
867
|
+
tableRoutes.get("/:schema/:table/distinct-values/:column", async (req, res) => {
|
|
868
|
+
try {
|
|
869
|
+
const { schema, table, column } = req.params;
|
|
870
|
+
const searchQuery = req.query.search;
|
|
871
|
+
const columnsParam = req.query.columns;
|
|
872
|
+
const pool = getConnection();
|
|
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) {
|
|
878
|
+
return res.status(400).json({ error: "Not connected to database" });
|
|
879
|
+
}
|
|
880
|
+
const dialect = getDialect();
|
|
881
|
+
const columnQuery = `
|
|
882
|
+
SELECT
|
|
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
|
|
889
|
+
LEFT JOIN (
|
|
890
|
+
SELECT
|
|
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)}
|
|
911
|
+
`;
|
|
912
|
+
const columnResult = await executeQuery(columnQuery, [
|
|
913
|
+
{ name: "schema", value: schema },
|
|
914
|
+
{ name: "table", value: table },
|
|
915
|
+
{ name: "column", value: column }
|
|
916
|
+
]);
|
|
917
|
+
if (columnResult.length === 0) {
|
|
918
|
+
return res.status(400).json({ error: "Column not found" });
|
|
919
|
+
}
|
|
920
|
+
const columnInfo = columnResult[0];
|
|
921
|
+
const isForeignKey = !!(columnInfo.referencedSchema || columnInfo.referenced_schema) && !!(columnInfo.referencedTable || columnInfo.referenced_table);
|
|
922
|
+
let query = "";
|
|
923
|
+
const params = [];
|
|
924
|
+
let displayColumn = null;
|
|
925
|
+
if (isForeignKey) {
|
|
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);
|
|
932
|
+
let columnsToSelect = [];
|
|
933
|
+
if (columnsParam) {
|
|
934
|
+
columnsToSelect = columnsParam.split(",").map((c) => c.trim()).filter((c) => c);
|
|
935
|
+
}
|
|
936
|
+
if (columnsToSelect.length > 0) {
|
|
937
|
+
const selectCols = columnsToSelect.map((col) => dialect.quoteId(col)).join(", ");
|
|
938
|
+
query = `SELECT DISTINCT ${selectCols} FROM ${quotedRefSchema}.${quotedRefTable}`;
|
|
939
|
+
if (searchQuery && 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()}%` });
|
|
943
|
+
} else {
|
|
944
|
+
query += ` WHERE ${quotedRefColumn} IS NOT NULL`;
|
|
945
|
+
}
|
|
946
|
+
const orderByCol = columnsToSelect.length > 1 ? columnsToSelect[1] : columnsToSelect[0];
|
|
947
|
+
query += ` ORDER BY ${dialect.quoteId(orderByCol)} ${dialect.limitOffset(0, 1e3)}`;
|
|
948
|
+
} else {
|
|
949
|
+
const refColumnsQuery = `
|
|
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
|
|
954
|
+
`;
|
|
955
|
+
const refColumns = await executeQuery(refColumnsQuery, [
|
|
956
|
+
{ name: "refSchema", value: refSchema },
|
|
957
|
+
{ name: "refTable", value: refTable }
|
|
958
|
+
]);
|
|
959
|
+
const preferredNames = ["name", "title", "description", "code"];
|
|
960
|
+
for (const preferredName of preferredNames) {
|
|
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
|
+
});
|
|
965
|
+
if (found) {
|
|
966
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (!displayColumn) {
|
|
971
|
+
const stringTypes = ["varchar", "nvarchar", "char", "nchar", "text", "ntext"];
|
|
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
|
+
});
|
|
977
|
+
if (found) {
|
|
978
|
+
displayColumn = found.column_name || found.COLUMN_NAME;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const quotedDisplayColumn = displayColumn ? dialect.quoteId(displayColumn) : null;
|
|
982
|
+
const selectCols = displayColumn ? `${quotedRefColumn}, ${quotedDisplayColumn}` : quotedRefColumn;
|
|
983
|
+
query = `SELECT DISTINCT ${selectCols} FROM ${quotedRefSchema}.${quotedRefTable}`;
|
|
984
|
+
if (searchQuery && searchQuery.trim()) {
|
|
985
|
+
if (displayColumn) {
|
|
986
|
+
query += ` WHERE ${quotedDisplayColumn} LIKE ${dialect.param(1)} OR ${quotedRefColumn} LIKE ${dialect.param(1)}`;
|
|
987
|
+
} else {
|
|
988
|
+
query += ` WHERE ${quotedRefColumn} LIKE ${dialect.param(1)}`;
|
|
989
|
+
}
|
|
990
|
+
params.push({ name: "search", value: `%${searchQuery.trim()}%` });
|
|
991
|
+
query += ` AND ${quotedRefColumn} IS NOT NULL`;
|
|
992
|
+
} else {
|
|
993
|
+
query += ` WHERE ${quotedRefColumn} IS NOT NULL`;
|
|
994
|
+
}
|
|
995
|
+
query += ` ORDER BY ${displayColumn ? quotedDisplayColumn : quotedRefColumn} ${dialect.limitOffset(0, 1e3)}`;
|
|
996
|
+
}
|
|
997
|
+
} else {
|
|
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);
|
|
1002
|
+
const columnsToSelect = columnsParam ? columnsParam.split(",").map((c) => c.trim()).filter((c) => c) : [];
|
|
1003
|
+
if (columnsToSelect.length > 0) {
|
|
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}`;
|
|
1008
|
+
if (searchQuery && 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()}%` });
|
|
1012
|
+
} else {
|
|
1013
|
+
query += ` WHERE ${keyColumn} IS NOT NULL`;
|
|
1014
|
+
}
|
|
1015
|
+
query += ` ORDER BY ${orderByColumn} ${dialect.limitOffset(0, 1e3)}`;
|
|
1016
|
+
} else {
|
|
1017
|
+
query = `SELECT DISTINCT ${quotedColumn} FROM ${quotedSchema}.${quotedTable}`;
|
|
1018
|
+
if (searchQuery && searchQuery.trim()) {
|
|
1019
|
+
if (["varchar", "nvarchar", "char", "nchar", "text", "ntext"].some((t) => dataType.includes(t))) {
|
|
1020
|
+
query += ` WHERE ${quotedColumn} LIKE ${dialect.param(1)}`;
|
|
1021
|
+
params.push({ name: "search", value: `%${searchQuery.trim()}%` });
|
|
1022
|
+
query += ` AND ${quotedColumn} IS NOT NULL`;
|
|
1023
|
+
} else {
|
|
1024
|
+
query += ` WHERE ${quotedColumn} IS NOT NULL`;
|
|
1025
|
+
}
|
|
1026
|
+
} else {
|
|
1027
|
+
query += ` WHERE ${quotedColumn} IS NOT NULL`;
|
|
1028
|
+
}
|
|
1029
|
+
query += ` ORDER BY ${quotedColumn} ${dialect.limitOffset(0, 1e3)}`;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
const result = await executeQuery(query, params);
|
|
1033
|
+
res.json(result);
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
console.error("Error fetching distinct values:", error);
|
|
1036
|
+
const errorMessage = error.message || "";
|
|
1037
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication") || errorMessage.includes("password authentication")) {
|
|
1038
|
+
const { disconnect: disconnect2 } = await import("./db-OIJ2PJK6.js");
|
|
1039
|
+
await disconnect2();
|
|
1040
|
+
}
|
|
1041
|
+
res.status(500).json({ error: error.message || "Failed to fetch distinct values" });
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
645
1044
|
|
|
646
1045
|
// src/server/routes/query.ts
|
|
647
1046
|
import { Router as Router3 } from "express";
|
|
648
1047
|
var queryRoutes = Router3();
|
|
649
1048
|
queryRoutes.post("/", async (req, res) => {
|
|
650
1049
|
try {
|
|
651
|
-
const { query: sqlQuery } = req.body;
|
|
1050
|
+
const { query: sqlQuery, queryId } = req.body;
|
|
652
1051
|
if (!sqlQuery || typeof sqlQuery !== "string") {
|
|
653
1052
|
return res.status(400).json({ error: "Query is required" });
|
|
654
1053
|
}
|
|
1054
|
+
const activeQueryId = queryId || generateQueryId();
|
|
655
1055
|
const startTime = Date.now();
|
|
656
|
-
const { recordsets: resultSets, columnMetadata } = await executeQueryMultiple(sqlQuery);
|
|
1056
|
+
const { recordsets: resultSets, columnMetadata } = await executeQueryMultiple(sqlQuery, void 0, activeQueryId);
|
|
657
1057
|
const executionTime = Date.now() - startTime;
|
|
658
1058
|
res.json({
|
|
659
1059
|
data: resultSets[0] || [],
|
|
660
1060
|
resultSets: resultSets.length > 0 ? resultSets : [],
|
|
661
1061
|
executionTime,
|
|
662
|
-
columnMetadata
|
|
1062
|
+
columnMetadata,
|
|
663
1063
|
// Include column metadata for empty result sets
|
|
1064
|
+
queryId: activeQueryId
|
|
1065
|
+
// Return queryId so client can track it
|
|
664
1066
|
});
|
|
665
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
|
+
}
|
|
666
1074
|
const errorMessage = error.message || "";
|
|
667
|
-
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
668
|
-
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");
|
|
669
1077
|
await disconnect2();
|
|
670
1078
|
}
|
|
671
1079
|
res.status(500).json({
|
|
@@ -674,6 +1082,24 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
674
1082
|
});
|
|
675
1083
|
}
|
|
676
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
|
+
});
|
|
677
1103
|
|
|
678
1104
|
// src/server/index.ts
|
|
679
1105
|
var __filename = fileURLToPath(import.meta.url);
|
|
@@ -734,7 +1160,7 @@ function getProvidedConnectionString() {
|
|
|
734
1160
|
// src/cli/index.ts
|
|
735
1161
|
import open from "open";
|
|
736
1162
|
var program = new Command();
|
|
737
|
-
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) => {
|
|
738
1164
|
const port = parseInt(options?.port || "4983", 10);
|
|
739
1165
|
try {
|
|
740
1166
|
const server = await startServer(port, connectionString);
|
|
@@ -742,7 +1168,8 @@ program.name("datapeek").description("A local SQL database browser").version("0.
|
|
|
742
1168
|
console.log(`
|
|
743
1169
|
\u{1F680} Datapeek server running at ${url}`);
|
|
744
1170
|
if (connectionString) {
|
|
745
|
-
|
|
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)}...`);
|
|
746
1173
|
}
|
|
747
1174
|
console.log(`
|
|
748
1175
|
Press Ctrl+C to stop
|