admindb 2.2.1 → 2.3.1
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 +368 -364
- package/dist/app.js +25 -16
- package/dist/auth/config.d.ts +1 -1
- package/dist/auth/config.js +18 -30
- package/dist/auth/crypto.js +11 -21
- package/dist/auth/middleware.js +9 -16
- package/dist/auth/routes.js +9 -15
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.js +20 -29
- package/dist/cli/config.d.ts +1 -1
- package/dist/cli/config.js +15 -18
- package/dist/cli/runner.js +13 -13
- package/dist/data/chain.d.ts +1 -1
- package/dist/data/chain.js +40 -617
- package/dist/data/datasets.d.ts +1 -1
- package/dist/data/datasets.js +43 -1
- package/dist/data/detector.js +45 -21
- package/dist/data/engine.d.ts +1 -1
- package/dist/data/engine.js +483 -142
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.js +8 -0
- package/dist/data/prng.d.ts +1 -0
- package/dist/data/prng.js +73 -0
- package/dist/data/registry.d.ts +1 -0
- package/dist/data/registry.js +805 -0
- package/dist/data/schema-graph.d.ts +1 -0
- package/dist/data/schema-graph.js +210 -0
- package/dist/data/strategies.d.ts +1 -1
- package/dist/data/strategies.js +44 -283
- package/dist/data/templates.d.ts +1 -0
- package/dist/data/templates.js +134 -0
- package/dist/data/types.d.ts +1 -1
- package/dist/data/validator.d.ts +1 -0
- package/dist/data/validator.js +160 -0
- package/dist/db/database.js +9 -14
- package/dist/db/export.js +32 -55
- package/dist/db/filters.d.ts +1 -1
- package/dist/db/filters.js +15 -38
- package/dist/db/introspection.js +4 -7
- package/dist/db/manager.d.ts +1 -1
- package/dist/db/manager.js +48 -53
- package/dist/db/migrations.js +7 -14
- package/dist/db/postgres.js +30 -78
- package/dist/index.d.ts +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/inspector.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/seed.js +1 -1
- package/dist/routes/api/erd.js +3 -8
- package/dist/routes/api/helpers.d.ts +1 -1
- package/dist/routes/api/helpers.js +75 -25
- package/dist/routes/api/import-export.js +1 -2
- package/dist/routes/api/query.js +2 -6
- package/dist/routes/api/rows.js +4 -8
- package/dist/routes/api/seed.js +35 -21
- package/dist/routes/api/tables.js +15 -30
- package/dist/routes/databases.js +8 -26
- package/dist/routes/pages.js +67 -18
- package/dist/serverless.js +31 -44
- package/dist/sql/generator.d.ts +1 -1
- package/dist/sql/generator.js +35 -109
- package/dist/types/api.d.ts +1 -1
- package/dist/utils/common.d.ts +1 -1
- package/dist/utils/common.js +45 -85
- package/dist/utils/csv.d.ts +1 -1
- package/dist/utils/csv.js +11 -17
- package/dist/utils/datatype.js +46 -60
- package/dist/utils/icons.js +8 -8
- package/dist/views/layouts/main.hbs +1 -87
- package/dist/views/pages/databases.hbs +1 -233
- package/dist/views/pages/designer.hbs +1 -89
- package/dist/views/pages/erd.hbs +1 -1241
- package/dist/views/pages/error.hbs +1 -10
- package/dist/views/pages/form.hbs +1 -59
- package/dist/views/pages/home.hbs +1 -220
- package/dist/views/pages/info.hbs +1 -107
- package/dist/views/pages/login.hbs +1 -98
- package/dist/views/pages/query.hbs +1 -129
- package/dist/views/pages/schema.hbs +1 -298
- package/dist/views/pages/seed-select.hbs +251 -0
- package/dist/views/pages/seed.hbs +256 -414
- package/dist/views/pages/table.hbs +1 -327
- package/dist/views/partials/navbar.hbs +1 -83
- package/dist/views/partials/sidebar.hbs +1 -129
- package/docs/API.md +149 -149
- package/docs/EXAMPLES.md +579 -579
- package/docs/SECURITY.md +265 -265
- package/package.json +80 -79
package/dist/data/chain.js
CHANGED
|
@@ -1,314 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildErGraph =
|
|
4
|
-
exports.resolveErChain = resolveErChain;
|
|
5
|
-
exports.topologicalSort = topologicalSort;
|
|
3
|
+
exports.topologicalSort = exports.resolveErChain = exports.buildErGraph = exports.buildChainInsertSql = void 0;
|
|
6
4
|
exports.getErChainConfig = getErChainConfig;
|
|
7
5
|
exports.generateChainRows = generateChainRows;
|
|
8
|
-
exports.buildChainInsertSql = buildChainInsertSql;
|
|
9
6
|
exports.executeChainInsert = executeChainInsert;
|
|
10
7
|
const generator_1 = require("../sql/generator");
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return [];
|
|
21
|
-
const refCol = fk.to || ref.data.primaryKey[0] || ref.data.columns[0].name;
|
|
22
|
-
if (!refCol)
|
|
23
|
-
return [];
|
|
24
|
-
const r = await db.all(`SELECT DISTINCT ${(0, generator_1.quoteIdentifier)(refCol)} AS v FROM ${(0, generator_1.quoteIdentifier)(fk.table)} WHERE ${(0, generator_1.quoteIdentifier)(refCol)} IS NOT NULL LIMIT 1000`);
|
|
25
|
-
return (r.data ?? []).map((row) => row.v).filter((v) => v != null);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function stringifyKey(v) {
|
|
32
|
-
if (v instanceof Uint8Array || Buffer.isBuffer(v))
|
|
33
|
-
return `buf:${Buffer.from(v).toString('hex')}`;
|
|
34
|
-
return `${typeof v}:${String(v)}`;
|
|
35
|
-
}
|
|
36
|
-
function orderedValue(value, base, dir, orEqual) {
|
|
37
|
-
const v = value;
|
|
38
|
-
const b = base;
|
|
39
|
-
if (typeof v === 'string' && typeof b === 'string') {
|
|
40
|
-
const tv = Date.parse(v);
|
|
41
|
-
const tb = Date.parse(b);
|
|
42
|
-
if (Number.isFinite(tv) && Number.isFinite(tb)) {
|
|
43
|
-
if (dir === 'after') {
|
|
44
|
-
if (orEqual ? tv < tb : tv <= tb)
|
|
45
|
-
return new Date(tb + 1000).toISOString();
|
|
46
|
-
}
|
|
47
|
-
else if (orEqual ? tv > tb : tv >= tb) {
|
|
48
|
-
return new Date(tb - 1000).toISOString();
|
|
49
|
-
}
|
|
50
|
-
return v;
|
|
51
|
-
}
|
|
52
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(v) && /^\d{4}-\d{2}-\d{2}$/.test(b)) {
|
|
53
|
-
const dv = Date.parse(`${v}T00:00:00Z`);
|
|
54
|
-
const db = Date.parse(`${b}T00:00:00Z`);
|
|
55
|
-
if (dir === 'after') {
|
|
56
|
-
if (orEqual ? dv < db : dv <= db)
|
|
57
|
-
return new Date(db + 86400_000).toISOString().slice(0, 10);
|
|
58
|
-
}
|
|
59
|
-
else if (orEqual ? dv > db : dv >= db) {
|
|
60
|
-
return new Date(db - 86400_000).toISOString().slice(0, 10);
|
|
61
|
-
}
|
|
62
|
-
return v;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (typeof v === 'number' && typeof b === 'number') {
|
|
66
|
-
if (dir === 'after')
|
|
67
|
-
return orEqual ? Math.max(v, b) : Math.max(v, b + 1);
|
|
68
|
-
return orEqual ? Math.min(v, b) : Math.min(v, b - 1);
|
|
69
|
-
}
|
|
70
|
-
return v;
|
|
71
|
-
}
|
|
72
|
-
function fitStringLength(val, len) {
|
|
73
|
-
if (!len || typeof val !== 'string')
|
|
74
|
-
return val;
|
|
75
|
-
let v = val;
|
|
76
|
-
if (len.exact != null) {
|
|
77
|
-
if (v.length > len.exact)
|
|
78
|
-
v = v.slice(0, len.exact);
|
|
79
|
-
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
80
|
-
while (v.length < len.exact) {
|
|
81
|
-
v += chars[Math.floor(Math.random() * chars.length)];
|
|
82
|
-
}
|
|
83
|
-
return v;
|
|
84
|
-
}
|
|
85
|
-
if (len.max != null && v.length > len.max) {
|
|
86
|
-
v = v.slice(0, len.max);
|
|
87
|
-
}
|
|
88
|
-
if (len.min != null && v.length < len.min) {
|
|
89
|
-
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
90
|
-
while (v.length < len.min) {
|
|
91
|
-
v += chars[Math.floor(Math.random() * chars.length)];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return v;
|
|
95
|
-
}
|
|
96
|
-
function applyOrdering(fields, ordering) {
|
|
97
|
-
if (!ordering.size)
|
|
98
|
-
return;
|
|
99
|
-
const byCol = new Map(fields.map((f) => [f.column, f]));
|
|
100
|
-
for (const f of fields) {
|
|
101
|
-
const rel = ordering.get(f.column);
|
|
102
|
-
if (!rel)
|
|
103
|
-
continue;
|
|
104
|
-
if (rel.after && f.value != null) {
|
|
105
|
-
const other = byCol.get(rel.after.column);
|
|
106
|
-
if (other && other.value != null)
|
|
107
|
-
f.value = orderedValue(f.value, other.value, 'after', rel.after.orEqual);
|
|
108
|
-
}
|
|
109
|
-
if (rel.before && f.value != null) {
|
|
110
|
-
const other = byCol.get(rel.before.column);
|
|
111
|
-
if (other && other.value != null)
|
|
112
|
-
f.value = orderedValue(f.value, other.value, 'before', rel.before.orEqual);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
function buildErGraph(tables) {
|
|
117
|
-
const graph = new Map();
|
|
118
|
-
for (const info of tables) {
|
|
119
|
-
const columns = (0, detector_1.buildColumnConfigs)(info);
|
|
120
|
-
const colMap = new Map(info.columns.map((c) => [c.name, c]));
|
|
121
|
-
const parents = [];
|
|
122
|
-
let hasSelfRef = false;
|
|
123
|
-
for (const fk of info.foreignKeys) {
|
|
124
|
-
const toCol = fk.to || (tables.find((t) => t.table === fk.table)?.primaryKey[0] ?? 'id');
|
|
125
|
-
if (fk.table === info.table) {
|
|
126
|
-
hasSelfRef = true;
|
|
127
|
-
}
|
|
128
|
-
const c = colMap.get(fk.from);
|
|
129
|
-
const notnull = Boolean(c?.notnull && !c?.pk);
|
|
130
|
-
parents.push({ table: fk.table, from: fk.from, to: toCol, notnull });
|
|
131
|
-
}
|
|
132
|
-
graph.set(info.table, {
|
|
133
|
-
name: info.table,
|
|
134
|
-
info,
|
|
135
|
-
columns,
|
|
136
|
-
parents,
|
|
137
|
-
children: [],
|
|
138
|
-
hasSelfRef,
|
|
139
|
-
depth: 0,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
for (const node of graph.values()) {
|
|
143
|
-
for (const p of node.parents) {
|
|
144
|
-
if (p.table !== node.name && graph.has(p.table)) {
|
|
145
|
-
graph.get(p.table).children.push({ table: node.name, from: p.from, to: p.to, notnull: p.notnull });
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return graph;
|
|
150
|
-
}
|
|
151
|
-
function resolveErChain(graph, rootTable, scope = 'chain') {
|
|
152
|
-
if (scope === 'all') {
|
|
153
|
-
return Array.from(graph.values());
|
|
154
|
-
}
|
|
155
|
-
const root = graph.get(rootTable);
|
|
156
|
-
if (!root)
|
|
157
|
-
return [];
|
|
158
|
-
if (scope === 'single') {
|
|
159
|
-
return [root];
|
|
160
|
-
}
|
|
161
|
-
const selected = new Set();
|
|
162
|
-
if (scope === 'ancestors') {
|
|
163
|
-
const queue = [rootTable];
|
|
164
|
-
selected.add(rootTable);
|
|
165
|
-
while (queue.length > 0) {
|
|
166
|
-
const current = queue.shift();
|
|
167
|
-
const node = graph.get(current);
|
|
168
|
-
if (!node)
|
|
169
|
-
continue;
|
|
170
|
-
for (const p of node.parents) {
|
|
171
|
-
if (p.table !== current && graph.has(p.table) && !selected.has(p.table)) {
|
|
172
|
-
selected.add(p.table);
|
|
173
|
-
queue.push(p.table);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
else if (scope === 'descendants') {
|
|
179
|
-
const queue = [rootTable];
|
|
180
|
-
selected.add(rootTable);
|
|
181
|
-
while (queue.length > 0) {
|
|
182
|
-
const current = queue.shift();
|
|
183
|
-
const node = graph.get(current);
|
|
184
|
-
if (!node)
|
|
185
|
-
continue;
|
|
186
|
-
for (const c of node.children) {
|
|
187
|
-
if (c.table !== current && graph.has(c.table) && !selected.has(c.table)) {
|
|
188
|
-
selected.add(c.table);
|
|
189
|
-
queue.push(c.table);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
const queue = [rootTable];
|
|
196
|
-
selected.add(rootTable);
|
|
197
|
-
while (queue.length > 0) {
|
|
198
|
-
const current = queue.shift();
|
|
199
|
-
const node = graph.get(current);
|
|
200
|
-
if (!node)
|
|
201
|
-
continue;
|
|
202
|
-
for (const p of node.parents) {
|
|
203
|
-
if (p.table !== current && graph.has(p.table) && !selected.has(p.table)) {
|
|
204
|
-
selected.add(p.table);
|
|
205
|
-
queue.push(p.table);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
for (const c of node.children) {
|
|
209
|
-
if (c.table !== current && graph.has(c.table) && !selected.has(c.table)) {
|
|
210
|
-
selected.add(c.table);
|
|
211
|
-
queue.push(c.table);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return Array.from(selected).map((name) => graph.get(name));
|
|
217
|
-
}
|
|
218
|
-
function topologicalSort(nodes) {
|
|
219
|
-
const activeNames = new Set(nodes.map((n) => n.name));
|
|
220
|
-
const nodeMap = new Map(nodes.map((n) => [n.name, n]));
|
|
221
|
-
const hardInDegree = new Map();
|
|
222
|
-
const totalInDegree = new Map();
|
|
223
|
-
const hardForwardEdges = new Map();
|
|
224
|
-
const softForwardEdges = new Map();
|
|
225
|
-
for (const n of nodes) {
|
|
226
|
-
hardInDegree.set(n.name, 0);
|
|
227
|
-
totalInDegree.set(n.name, 0);
|
|
228
|
-
hardForwardEdges.set(n.name, new Set());
|
|
229
|
-
softForwardEdges.set(n.name, new Set());
|
|
230
|
-
}
|
|
231
|
-
for (const n of nodes) {
|
|
232
|
-
for (const p of n.parents) {
|
|
233
|
-
if (p.table !== n.name && activeNames.has(p.table)) {
|
|
234
|
-
if (p.notnull) {
|
|
235
|
-
if (!hardForwardEdges.get(p.table).has(n.name)) {
|
|
236
|
-
hardForwardEdges.get(p.table).add(n.name);
|
|
237
|
-
hardInDegree.set(n.name, (hardInDegree.get(n.name) ?? 0) + 1);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
if (!softForwardEdges.get(p.table).has(n.name)) {
|
|
242
|
-
softForwardEdges.get(p.table).add(n.name);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
totalInDegree.set(n.name, (totalInDegree.get(n.name) ?? 0) + 1);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
const queue = [];
|
|
250
|
-
const visited = new Set();
|
|
251
|
-
for (const [name, deg] of hardInDegree.entries()) {
|
|
252
|
-
if (deg === 0) {
|
|
253
|
-
queue.push(name);
|
|
254
|
-
visited.add(name);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
const sortedNames = [];
|
|
258
|
-
const depths = new Map();
|
|
259
|
-
for (const name of queue)
|
|
260
|
-
depths.set(name, 0);
|
|
261
|
-
let cycleDetected = false;
|
|
262
|
-
const cycleTables = [];
|
|
263
|
-
while (visited.size < nodes.length) {
|
|
264
|
-
while (queue.length > 0) {
|
|
265
|
-
const curr = queue.shift();
|
|
266
|
-
sortedNames.push(curr);
|
|
267
|
-
const currDepth = depths.get(curr) ?? 0;
|
|
268
|
-
for (const child of hardForwardEdges.get(curr) ?? []) {
|
|
269
|
-
const newDeg = (hardInDegree.get(child) ?? 1) - 1;
|
|
270
|
-
hardInDegree.set(child, newDeg);
|
|
271
|
-
depths.set(child, Math.max(depths.get(child) ?? 0, currDepth + 1));
|
|
272
|
-
if (newDeg === 0 && !visited.has(child)) {
|
|
273
|
-
visited.add(child);
|
|
274
|
-
queue.push(child);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
for (const child of softForwardEdges.get(curr) ?? []) {
|
|
278
|
-
depths.set(child, Math.max(depths.get(child) ?? 0, currDepth + 1));
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
if (visited.size < nodes.length) {
|
|
282
|
-
cycleDetected = true;
|
|
283
|
-
let bestCandidate = '';
|
|
284
|
-
let lowestDeg = Infinity;
|
|
285
|
-
for (const n of nodes) {
|
|
286
|
-
if (!visited.has(n.name)) {
|
|
287
|
-
const deg = hardInDegree.get(n.name) ?? 0;
|
|
288
|
-
if (deg < lowestDeg) {
|
|
289
|
-
lowestDeg = deg;
|
|
290
|
-
bestCandidate = n.name;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (bestCandidate) {
|
|
295
|
-
cycleTables.push(bestCandidate);
|
|
296
|
-
visited.add(bestCandidate);
|
|
297
|
-
depths.set(bestCandidate, (depths.get(bestCandidate) ?? 1) + 1);
|
|
298
|
-
queue.push(bestCandidate);
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
const sorted = sortedNames.map((name) => {
|
|
306
|
-
const node = { ...nodeMap.get(name) };
|
|
307
|
-
node.depth = depths.get(name) ?? 0;
|
|
308
|
-
return node;
|
|
309
|
-
});
|
|
310
|
-
return { sorted, cycleDetected, cycleTables };
|
|
311
|
-
}
|
|
8
|
+
const engine_1 = require("./engine");
|
|
9
|
+
Object.defineProperty(exports, "buildChainInsertSql", { enumerable: true, get: function () { return engine_1.buildChainInsertSql; } });
|
|
10
|
+
const schema_graph_1 = require("./schema-graph");
|
|
11
|
+
const buildErGraph = (tables) => (0, schema_graph_1.buildSchemaGraph)(tables).nodes;
|
|
12
|
+
exports.buildErGraph = buildErGraph;
|
|
13
|
+
const resolveErChain = (graph, rootTable, scope = 'chain') => (0, schema_graph_1.resolveScopeTables)({ nodes: graph, relationships: [] }, rootTable, scope);
|
|
14
|
+
exports.resolveErChain = resolveErChain;
|
|
15
|
+
const topologicalSort = (nodes) => (0, schema_graph_1.sortTopologically)(nodes);
|
|
16
|
+
exports.topologicalSort = topologicalSort;
|
|
312
17
|
async function getErChainConfig(db, rootTable, scope = 'chain') {
|
|
313
18
|
const tableListRes = await db.listTables();
|
|
314
19
|
const tableNames = (tableListRes.data ?? [])
|
|
@@ -320,9 +25,9 @@ async function getErChainConfig(db, rootTable, scope = 'chain') {
|
|
|
320
25
|
if (info.success && info.data)
|
|
321
26
|
tables.push(info.data);
|
|
322
27
|
}
|
|
323
|
-
const graph =
|
|
324
|
-
const chainNodes =
|
|
325
|
-
const { sorted, cycleDetected, cycleTables } =
|
|
28
|
+
const graph = (0, schema_graph_1.buildSchemaGraph)(tables);
|
|
29
|
+
const chainNodes = (0, schema_graph_1.resolveScopeTables)(graph, rootTable, scope);
|
|
30
|
+
const { sorted, cycleDetected, cycleTables } = (0, schema_graph_1.sortTopologically)(chainNodes);
|
|
326
31
|
const tableNodes = [];
|
|
327
32
|
for (let i = 0; i < sorted.length; i++) {
|
|
328
33
|
const node = sorted[i];
|
|
@@ -332,15 +37,8 @@ async function getErChainConfig(db, rootTable, scope = 'chain') {
|
|
|
332
37
|
if (countRes.success && typeof countRes.data === 'number')
|
|
333
38
|
rowCount = countRes.data;
|
|
334
39
|
}
|
|
335
|
-
catch {
|
|
336
|
-
|
|
337
|
-
let suggestedCount = 10;
|
|
338
|
-
if (node.depth === 0)
|
|
339
|
-
suggestedCount = 5;
|
|
340
|
-
else if (node.depth === 1)
|
|
341
|
-
suggestedCount = 15;
|
|
342
|
-
else
|
|
343
|
-
suggestedCount = 25;
|
|
40
|
+
catch { }
|
|
41
|
+
const suggestedCount = node.depth === 0 ? 5 : node.depth === 1 ? 15 : 25;
|
|
344
42
|
tableNodes.push({
|
|
345
43
|
name: node.name,
|
|
346
44
|
order: i + 1,
|
|
@@ -352,6 +50,8 @@ async function getErChainConfig(db, rootTable, scope = 'chain') {
|
|
|
352
50
|
rowCount,
|
|
353
51
|
isRoot: node.name === rootTable,
|
|
354
52
|
hasSelfRef: node.hasSelfRef,
|
|
53
|
+
isJunction: node.isJunction,
|
|
54
|
+
mode: 'generate',
|
|
355
55
|
});
|
|
356
56
|
}
|
|
357
57
|
return {
|
|
@@ -361,314 +61,38 @@ async function getErChainConfig(db, rootTable, scope = 'chain') {
|
|
|
361
61
|
cycleDetected,
|
|
362
62
|
cycleTables,
|
|
363
63
|
maxTotalRows: 10000,
|
|
64
|
+
relationships: graph.relationships,
|
|
364
65
|
};
|
|
365
66
|
}
|
|
366
|
-
async function generateChainRows(db, chainConfig, customPlans = {}, customCounts = {}, truncate = false) {
|
|
367
|
-
const
|
|
368
|
-
const executionOrder = chainConfig.tables.map((t) => t.name);
|
|
369
|
-
const relationalPool = new Map();
|
|
370
|
-
const tableResults = {};
|
|
371
|
-
let totalRows = 0;
|
|
67
|
+
async function generateChainRows(db, chainConfig, customPlans = {}, customCounts = {}, truncate = false, modes = {}, seed) {
|
|
68
|
+
const planTables = {};
|
|
372
69
|
for (const tableNode of chainConfig.tables) {
|
|
373
70
|
const tableName = tableNode.name;
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const fkByColumn = new Map();
|
|
384
|
-
for (const fk of info.foreignKeys)
|
|
385
|
-
fkByColumn.set(fk.from, fk);
|
|
386
|
-
const uniqueCols = new Set();
|
|
387
|
-
for (const cfg of configs)
|
|
388
|
-
if (cfg.unique)
|
|
389
|
-
uniqueCols.add(cfg.name);
|
|
390
|
-
const compositeUniques = [];
|
|
391
|
-
for (const ix of info.indexes ?? []) {
|
|
392
|
-
if (ix.unique && ix.columns.length === 1 && ix.columns[0])
|
|
393
|
-
uniqueCols.add(ix.columns[0]);
|
|
394
|
-
if (ix.unique && ix.columns.length > 1)
|
|
395
|
-
compositeUniques.push(ix.columns);
|
|
396
|
-
}
|
|
397
|
-
if (info.sql) {
|
|
398
|
-
const uqRe = /UNIQUE\s*\(\s*([^)]+)\s*\)/gi;
|
|
399
|
-
let uqM;
|
|
400
|
-
while ((uqM = uqRe.exec(info.sql))) {
|
|
401
|
-
const rawCols = uqM[1].split(',').map((s) => s.trim().replace(/[`"\[\]]/g, ''));
|
|
402
|
-
if (rawCols.length === 1 && rawCols[0]) {
|
|
403
|
-
uniqueCols.add(rawCols[0]);
|
|
404
|
-
}
|
|
405
|
-
else if (rawCols.length > 1 && !compositeUniques.some((cu) => cu.join(',') === rawCols.join(','))) {
|
|
406
|
-
compositeUniques.push(rawCols);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
const compositeSeen = new Map();
|
|
411
|
-
for (const cu of compositeUniques) {
|
|
412
|
-
compositeSeen.set(cu.join('::'), new Set());
|
|
413
|
-
}
|
|
414
|
-
const checkInfo = (0, detector_1.parseChecks)(info.sql ?? null);
|
|
415
|
-
const prepared = [];
|
|
416
|
-
const fkPools = new Map();
|
|
417
|
-
const fkOmit = new Set();
|
|
418
|
-
for (const col of info.columns) {
|
|
419
|
-
const cfg = configs.find((c) => c.name === col.name);
|
|
420
|
-
const raw = tablePlan[col.name];
|
|
421
|
-
const p = raw && raw.strategy ? raw : (cfg?.defaultPlan ?? { strategy: 'skip' });
|
|
422
|
-
if (p.strategy === 'list' && (!p.values || !p.values.length)) {
|
|
423
|
-
const allowed = (0, detector_1.findInMap)(checkInfo.columnAllowed, col.name);
|
|
424
|
-
if (allowed && allowed.length)
|
|
425
|
-
p.values = [...allowed];
|
|
426
|
-
}
|
|
427
|
-
if (p.strategy === 'fk') {
|
|
428
|
-
const fk = fkByColumn.get(col.name);
|
|
429
|
-
const refTable = fk?.table ?? '';
|
|
430
|
-
const refCol = fk?.to || 'id';
|
|
431
|
-
const poolKey = `${refTable}.${refCol}`;
|
|
432
|
-
let pool = relationalPool.get(poolKey) ?? [];
|
|
433
|
-
if (!pool.length) {
|
|
434
|
-
for (const [k, v] of relationalPool.entries()) {
|
|
435
|
-
if (k.toLowerCase() === poolKey.toLowerCase() && v.length) {
|
|
436
|
-
pool = v;
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
if (!pool.length) {
|
|
442
|
-
pool = await sampleFkValues(db, fk ?? null);
|
|
443
|
-
}
|
|
444
|
-
fkPools.set(col.name, pool);
|
|
445
|
-
if (!pool.length) {
|
|
446
|
-
if (col.notnull && !col.pk) {
|
|
447
|
-
pool = [1];
|
|
448
|
-
fkPools.set(col.name, pool);
|
|
449
|
-
warnings.push(`Table "${tableName}" Column "${col.name}" is a NOT NULL foreign key to "${refTable}". Fallback ID 1 was used.`);
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
warnings.push(`Table "${tableName}" Column "${col.name}": "${refTable}" has no rows; NULL will be used.`);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
prepared.push({ col, plan: p });
|
|
457
|
-
}
|
|
458
|
-
const rows = [];
|
|
459
|
-
const previewRows = [];
|
|
460
|
-
const uniqueSeen = new Map();
|
|
461
|
-
if (!truncate) {
|
|
462
|
-
for (const col of uniqueCols) {
|
|
463
|
-
try {
|
|
464
|
-
const existing = await db.all(`SELECT ${(0, generator_1.quoteIdentifier)(col)} AS v FROM ${(0, generator_1.quoteIdentifier)(tableName)} WHERE ${(0, generator_1.quoteIdentifier)(col)} IS NOT NULL LIMIT 10000`);
|
|
465
|
-
const seen = new Set();
|
|
466
|
-
for (const r of (existing.data ?? [])) {
|
|
467
|
-
if (r.v != null)
|
|
468
|
-
seen.add(stringifyKey(r.v));
|
|
469
|
-
}
|
|
470
|
-
uniqueSeen.set(col, seen);
|
|
471
|
-
}
|
|
472
|
-
catch { }
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
const tableGeneratedPkVals = {};
|
|
476
|
-
let pkStartOffset = 0;
|
|
477
|
-
for (const col of info.columns) {
|
|
478
|
-
if (col.pk && (col.type || '').toUpperCase().includes('INT')) {
|
|
479
|
-
try {
|
|
480
|
-
const maxRes = await db.all(`SELECT MAX(${(0, generator_1.quoteIdentifier)(col.name)}) AS mx FROM ${(0, generator_1.quoteIdentifier)(tableName)}`);
|
|
481
|
-
const maxVal = (maxRes.data ?? [])[0]?.mx;
|
|
482
|
-
if (typeof maxVal === 'number' && Number.isFinite(maxVal))
|
|
483
|
-
pkStartOffset = maxVal;
|
|
484
|
-
}
|
|
485
|
-
catch {
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
const isUniqueCol = (name) => {
|
|
490
|
-
const norm = name.toLowerCase().replace(/[`"\[\]]/g, '');
|
|
491
|
-
for (const u of uniqueCols) {
|
|
492
|
-
if (u.toLowerCase().replace(/[`"\[\]]/g, '') === norm)
|
|
493
|
-
return true;
|
|
494
|
-
}
|
|
495
|
-
return false;
|
|
496
|
-
};
|
|
497
|
-
for (const { col, plan: p } of prepared) {
|
|
498
|
-
if (p.strategy === 'fk' && (col.pk || isUniqueCol(col.name))) {
|
|
499
|
-
const pool = fkPools.get(col.name);
|
|
500
|
-
if (pool && pool.length > 0 && pool.length < count) {
|
|
501
|
-
count = pool.length;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
if (compositeUniques.length > 0) {
|
|
506
|
-
for (const cu of compositeUniques) {
|
|
507
|
-
const isAllFk = cu.every((colName) => prepared.some((p) => (p.col.name === colName || p.col.name.replace(/[`"\[\]]/g, '') === colName.replace(/[`"\[\]]/g, '')) && p.plan.strategy === 'fk'));
|
|
508
|
-
if (isAllFk) {
|
|
509
|
-
const maxCombos = cu.reduce((acc, colName) => {
|
|
510
|
-
const pool = fkPools.get(colName) ?? [];
|
|
511
|
-
return acc * Math.max(1, pool.length);
|
|
512
|
-
}, 1);
|
|
513
|
-
if (maxCombos > 0 && maxCombos < count) {
|
|
514
|
-
count = maxCombos;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
for (let i = 0; i < count; i++) {
|
|
520
|
-
let fields = [];
|
|
521
|
-
let previewRow = {};
|
|
522
|
-
let rowValid = false;
|
|
523
|
-
let attempt = 0;
|
|
524
|
-
while (!rowValid && attempt < 30) {
|
|
525
|
-
fields = [];
|
|
526
|
-
previewRow = {};
|
|
527
|
-
for (const { col, plan: p } of prepared) {
|
|
528
|
-
if (p.strategy === 'fk' && fkOmit.has(col.name)) {
|
|
529
|
-
previewRow[col.name] = null;
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
const isSelfFk = fkByColumn.get(col.name)?.table === tableName;
|
|
533
|
-
let pool = fkPools.get(col.name);
|
|
534
|
-
if (isSelfFk) {
|
|
535
|
-
const existingSelfIds = tableGeneratedPkVals[fkByColumn.get(col.name)?.to || info.primaryKey[0] || 'id'] ?? [];
|
|
536
|
-
if (i === 0 || existingSelfIds.length === 0 || (Math.random() < 0.35 && !col.notnull)) {
|
|
537
|
-
pool = [];
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
pool = existingSelfIds;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
let v = (0, strategies_1.generateOne)(col, p, pool, i + attempt * 50);
|
|
544
|
-
if (v === types_1.SKIP && col.pk && (col.type || '').toUpperCase().includes('INT')) {
|
|
545
|
-
v = pkStartOffset + i + 1;
|
|
546
|
-
}
|
|
547
|
-
if (v === types_1.SKIP) {
|
|
548
|
-
previewRow[col.name] = '(default)';
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
if (isUniqueCol(col.name)) {
|
|
552
|
-
const seen = uniqueSeen.get(col.name) ?? new Set();
|
|
553
|
-
if (p.strategy === 'fk') {
|
|
554
|
-
const unused = (pool ?? []).filter((x) => !seen.has(stringifyKey(x)));
|
|
555
|
-
if (unused.length > 0) {
|
|
556
|
-
v = unused[0];
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
rowValid = false;
|
|
560
|
-
break;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
let singleAtt = 0;
|
|
565
|
-
while (seen.has(stringifyKey(v)) && singleAtt < 100) {
|
|
566
|
-
v = (0, strategies_1.generateOne)(col, p, pool, i + attempt * 50 + singleAtt * 100);
|
|
567
|
-
singleAtt += 1;
|
|
568
|
-
}
|
|
569
|
-
if (seen.has(stringifyKey(v))) {
|
|
570
|
-
rowValid = false;
|
|
571
|
-
break;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
v = fitStringLength(v, (0, detector_1.findInMap)(checkInfo.columnLength, col.name));
|
|
576
|
-
fields.push({ column: col.name, value: v });
|
|
577
|
-
previewRow[col.name] = v;
|
|
578
|
-
}
|
|
579
|
-
applyOrdering(fields, checkInfo.ordering);
|
|
580
|
-
for (const f of fields) {
|
|
581
|
-
previewRow[f.column] = f.value;
|
|
582
|
-
}
|
|
583
|
-
let compositeCollision = false;
|
|
584
|
-
if (compositeUniques.length > 0) {
|
|
585
|
-
const fieldMap = new Map(fields.map((f) => [f.column, f.value]));
|
|
586
|
-
for (const cu of compositeUniques) {
|
|
587
|
-
const key = cu.join('::');
|
|
588
|
-
const compositeVal = cu.map((colName) => stringifyKey(fieldMap.get(colName))).join('|');
|
|
589
|
-
const seenSet = compositeSeen.get(key) ?? new Set();
|
|
590
|
-
if (seenSet.has(compositeVal)) {
|
|
591
|
-
compositeCollision = true;
|
|
592
|
-
break;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
if (!compositeCollision) {
|
|
597
|
-
rowValid = true;
|
|
598
|
-
if (compositeUniques.length > 0) {
|
|
599
|
-
const fieldMap = new Map(fields.map((f) => [f.column, f.value]));
|
|
600
|
-
for (const cu of compositeUniques) {
|
|
601
|
-
const key = cu.join('::');
|
|
602
|
-
const compositeVal = cu.map((colName) => stringifyKey(fieldMap.get(colName))).join('|');
|
|
603
|
-
const seenSet = compositeSeen.get(key) ?? new Set();
|
|
604
|
-
seenSet.add(compositeVal);
|
|
605
|
-
compositeSeen.set(key, seenSet);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
attempt += 1;
|
|
610
|
-
}
|
|
611
|
-
if (!rowValid) {
|
|
612
|
-
break;
|
|
613
|
-
}
|
|
614
|
-
for (const f of fields) {
|
|
615
|
-
if (isUniqueCol(f.column)) {
|
|
616
|
-
const seen = uniqueSeen.get(f.column) ?? new Set();
|
|
617
|
-
seen.add(stringifyKey(f.value));
|
|
618
|
-
uniqueSeen.set(f.column, seen);
|
|
619
|
-
}
|
|
620
|
-
const poolKey = `${tableName}.${f.column}`;
|
|
621
|
-
const cur = relationalPool.get(poolKey) ?? [];
|
|
622
|
-
cur.push(f.value);
|
|
623
|
-
relationalPool.set(poolKey, cur);
|
|
624
|
-
const selfCur = tableGeneratedPkVals[f.column] ?? [];
|
|
625
|
-
selfCur.push(f.value);
|
|
626
|
-
tableGeneratedPkVals[f.column] = selfCur;
|
|
627
|
-
}
|
|
628
|
-
rows.push(fields);
|
|
629
|
-
if (previewRows.length < 50) {
|
|
630
|
-
previewRows.push(previewRow);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
tableResults[tableName] = {
|
|
634
|
-
count: rows.length,
|
|
71
|
+
const mode = modes[tableName] || 'generate';
|
|
72
|
+
const rows = customCounts[tableName] ?? tableNode.suggestedCount ?? 10;
|
|
73
|
+
const columns = {};
|
|
74
|
+
for (const col of tableNode.columns) {
|
|
75
|
+
const raw = customPlans[tableName]?.[col.name];
|
|
76
|
+
columns[col.name] = raw && raw.strategy ? raw : col.defaultPlan;
|
|
77
|
+
}
|
|
78
|
+
planTables[tableName] = {
|
|
79
|
+
mode,
|
|
635
80
|
rows,
|
|
636
|
-
|
|
637
|
-
|
|
81
|
+
columns,
|
|
82
|
+
truncate,
|
|
638
83
|
};
|
|
639
|
-
totalRows += rows.length;
|
|
640
84
|
}
|
|
641
|
-
const
|
|
642
|
-
return {
|
|
85
|
+
const generationPlan = {
|
|
643
86
|
rootTable: chainConfig.rootTable,
|
|
644
87
|
scope: chainConfig.scope,
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
88
|
+
tables: planTables,
|
|
89
|
+
relationships: chainConfig.relationships,
|
|
90
|
+
options: {
|
|
91
|
+
seed: seed ?? null,
|
|
92
|
+
truncateAll: truncate,
|
|
93
|
+
},
|
|
650
94
|
};
|
|
651
|
-
|
|
652
|
-
function buildChainInsertSql(executionOrder, tableResults) {
|
|
653
|
-
const sqlParts = ['-- AdminDB ER Chain Seed Script', '-- Order of execution: ' + executionOrder.join(' -> '), 'BEGIN TRANSACTION;\n'];
|
|
654
|
-
for (const table of executionOrder) {
|
|
655
|
-
const res = tableResults[table];
|
|
656
|
-
if (!res || !res.rows.length)
|
|
657
|
-
continue;
|
|
658
|
-
sqlParts.push(`-- --------------------------------------------------------`);
|
|
659
|
-
sqlParts.push(`-- Table: ${table} (${res.rows.length} rows)`);
|
|
660
|
-
sqlParts.push(`-- --------------------------------------------------------`);
|
|
661
|
-
for (const fields of res.rows) {
|
|
662
|
-
if (!fields.length)
|
|
663
|
-
continue;
|
|
664
|
-
const cols = fields.map((f) => (0, generator_1.quoteIdentifier)(f.column)).join(', ');
|
|
665
|
-
const vals = fields.map((f) => (0, generator_1.sqlValue)(f.value)).join(', ');
|
|
666
|
-
sqlParts.push(`INSERT INTO ${(0, generator_1.quoteIdentifier)(table)} (${cols}) VALUES (${vals});`);
|
|
667
|
-
}
|
|
668
|
-
sqlParts.push('');
|
|
669
|
-
}
|
|
670
|
-
sqlParts.push('COMMIT;');
|
|
671
|
-
return sqlParts.join('\n');
|
|
95
|
+
return engine_1.SeedEngine.executePlan(db, generationPlan);
|
|
672
96
|
}
|
|
673
97
|
async function executeChainInsert(db, chainResult, truncate = false) {
|
|
674
98
|
const t0 = Date.now();
|
|
@@ -697,11 +121,10 @@ async function executeChainInsert(db, chainResult, truncate = false) {
|
|
|
697
121
|
inserted[table] = count;
|
|
698
122
|
totalInserted += count;
|
|
699
123
|
}
|
|
700
|
-
const elapsedMs = Date.now() - t0;
|
|
701
124
|
return {
|
|
702
125
|
inserted,
|
|
703
126
|
totalInserted,
|
|
704
|
-
elapsedMs,
|
|
127
|
+
elapsedMs: Date.now() - t0,
|
|
705
128
|
warnings: chainResult.warnings,
|
|
706
129
|
};
|
|
707
130
|
}
|