prostgles-server 4.2.612 → 4.2.613
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/DboBuilder/DboBuilder.d.ts +1 -1
- package/dist/DboBuilder/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder/DboBuilder.js +1 -1
- package/dist/DboBuilder/DboBuilder.js.map +1 -1
- package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts +1 -1
- package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts.map +1 -1
- package/dist/DboBuilder/joins/getInferredJoins.d.ts +4 -0
- package/dist/DboBuilder/joins/getInferredJoins.d.ts.map +1 -0
- package/dist/DboBuilder/joins/getInferredJoins.js +55 -0
- package/dist/DboBuilder/joins/getInferredJoins.js.map +1 -0
- package/dist/DboBuilder/joins/prepareShortestJoinPaths.d.ts +12 -0
- package/dist/DboBuilder/joins/prepareShortestJoinPaths.d.ts.map +1 -0
- package/dist/DboBuilder/joins/prepareShortestJoinPaths.js +165 -0
- package/dist/DboBuilder/joins/prepareShortestJoinPaths.js.map +1 -0
- package/dist/DboBuilder/joins/prepareShortestJoinPaths.spec.d.ts.map +1 -0
- package/dist/DboBuilder/joins/prepareShortestJoinPaths.spec.js +67 -0
- package/dist/DboBuilder/joins/prepareShortestJoinPaths.spec.js.map +1 -0
- package/dist/DboBuilder/joins/shortestPath.d.ts.map +1 -0
- package/dist/{shortestPath.js → DboBuilder/joins/shortestPath.js} +11 -11
- package/dist/DboBuilder/joins/shortestPath.js.map +1 -0
- package/dist/ProstglesTypes.d.ts +4 -3
- package/dist/ProstglesTypes.d.ts.map +1 -1
- package/dist/utils/utils.d.ts +1 -0
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +8 -3
- package/dist/utils/utils.js.map +1 -1
- package/lib/DboBuilder/DboBuilder.ts +13 -10
- package/lib/DboBuilder/ViewHandler/ViewHandler.ts +1 -1
- package/lib/DboBuilder/joins/getInferredJoins.ts +59 -0
- package/lib/DboBuilder/joins/prepareShortestJoinPaths.spec.ts +72 -0
- package/lib/DboBuilder/joins/prepareShortestJoinPaths.ts +205 -0
- package/lib/{shortestPath.ts → DboBuilder/joins/shortestPath.ts} +14 -13
- package/lib/ProstglesTypes.ts +4 -3
- package/lib/utils/utils.ts +6 -0
- package/package.json +1 -1
- package/tsconfig.json +1 -1
- package/dist/DboBuilder/prepareShortestJoinPaths.d.ts +0 -12
- package/dist/DboBuilder/prepareShortestJoinPaths.d.ts.map +0 -1
- package/dist/DboBuilder/prepareShortestJoinPaths.js +0 -186
- package/dist/DboBuilder/prepareShortestJoinPaths.js.map +0 -1
- package/dist/DboBuilder/prepareShortestJoinPaths.spec.d.ts.map +0 -1
- package/dist/DboBuilder/prepareShortestJoinPaths.spec.js +0 -30
- package/dist/DboBuilder/prepareShortestJoinPaths.spec.js.map +0 -1
- package/dist/shortestPath.d.ts.map +0 -1
- package/dist/shortestPath.js.map +0 -1
- package/lib/DboBuilder/prepareShortestJoinPaths.spec.ts +0 -29
- package/lib/DboBuilder/prepareShortestJoinPaths.ts +0 -225
- /package/dist/DboBuilder/{prepareShortestJoinPaths.spec.d.ts → joins/prepareShortestJoinPaths.spec.d.ts} +0 -0
- /package/dist/{shortestPath.d.ts → DboBuilder/joins/shortestPath.d.ts} +0 -0
|
@@ -11,7 +11,7 @@ import type { PublishParser } from "../PublishParser/PublishParser";
|
|
|
11
11
|
import type { OnCommitCallback } from "../PublishParser/publishTypesAndUtils";
|
|
12
12
|
import type { ServerFunctionDefinition } from "../PublishParser/defineServerFunction";
|
|
13
13
|
import { getQueryErrorPositionInfo } from "../TableConfig/runSQLFile";
|
|
14
|
-
import type { Graph } from "
|
|
14
|
+
import type { Graph } from "./joins/shortestPath";
|
|
15
15
|
import { clone } from "../utils/utils";
|
|
16
16
|
import type {
|
|
17
17
|
DBHandlerServer,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
getConstraints,
|
|
32
32
|
getSerializedClientErrorFromPGError,
|
|
33
33
|
} from "./dboBuilderUtils";
|
|
34
|
-
import { prepareShortestJoinPaths } from "./prepareShortestJoinPaths";
|
|
34
|
+
import { prepareShortestJoinPaths } from "./joins/prepareShortestJoinPaths";
|
|
35
35
|
import { cacheDBTypes, runSQL } from "./runSql/runSQL";
|
|
36
36
|
import { getDetailedFieldInfo, type getDbTypes } from "./runSql/runSqlUtils";
|
|
37
37
|
import { getTablesForSchemaPostgresSQL } from "./schema/getTablesForSchemaPostgresSQL";
|
|
@@ -140,10 +140,7 @@ export class DboBuilder {
|
|
|
140
140
|
|
|
141
141
|
onSchemaChange?: (event: { command: string; query: string }) => void;
|
|
142
142
|
|
|
143
|
-
private readonly onCommitCallbacksByTransaction = new WeakMap<
|
|
144
|
-
object,
|
|
145
|
-
OnCommitCallback[]
|
|
146
|
-
>();
|
|
143
|
+
private readonly onCommitCallbacksByTransaction = new WeakMap<object, OnCommitCallback[]>();
|
|
147
144
|
|
|
148
145
|
registerOnCommitCallback = (transaction: object, callback: OnCommitCallback) => {
|
|
149
146
|
const callbacks = this.onCommitCallbacksByTransaction.get(transaction);
|
|
@@ -250,9 +247,12 @@ export class DboBuilder {
|
|
|
250
247
|
this.checkingCanSubscribe = false;
|
|
251
248
|
}
|
|
252
249
|
const start = Date.now();
|
|
253
|
-
const tablesOrViewsReq = await getTablesForSchemaPostgresSQL(
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
const tablesOrViewsReq = await getTablesForSchemaPostgresSQL(
|
|
251
|
+
{ db: this.prostgles.dbForSchema! },
|
|
252
|
+
{
|
|
253
|
+
schemaFilter: this.prostgles.opts.schemaFilter,
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
256
|
await this.prostgles.opts.onLog?.({
|
|
257
257
|
type: "debug",
|
|
258
258
|
command: "DboBuilder.getTablesForSchemaPostgresSQL",
|
|
@@ -262,7 +262,10 @@ export class DboBuilder {
|
|
|
262
262
|
const tablesOrViews = tablesOrViewsReq.result;
|
|
263
263
|
this.tablesOrViews = tablesOrViews;
|
|
264
264
|
|
|
265
|
-
this.constraints = await getConstraints(
|
|
265
|
+
this.constraints = await getConstraints(
|
|
266
|
+
this.prostgles.dbForSchema!,
|
|
267
|
+
this.prostgles.opts.schemaFilter,
|
|
268
|
+
);
|
|
266
269
|
await this.prepareShortestJoinPaths();
|
|
267
270
|
|
|
268
271
|
this.dbo = {};
|
|
@@ -13,7 +13,7 @@ import type { TableEvent } from "../../Logging";
|
|
|
13
13
|
import type { DB } from "../../Prostgles";
|
|
14
14
|
import type { Join } from "../../ProstglesTypes";
|
|
15
15
|
import type { ParsedTableRule } from "../../PublishParser/PublishParser";
|
|
16
|
-
import type { Graph } from "
|
|
16
|
+
import type { Graph } from "../joins/shortestPath";
|
|
17
17
|
import type { DboBuilder, Filter, LocalParams, TableHandlers } from "../DboBuilder";
|
|
18
18
|
import { getSerializedClientErrorFromPGError } from "../DboBuilder";
|
|
19
19
|
import type { TableSchema } from "../DboBuilderTypes";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { TableSchema } from "../DboBuilderTypes";
|
|
2
|
+
import type { Join } from "../../ProstglesTypes";
|
|
3
|
+
import { sortedArraysMatch } from "../../utils/utils";
|
|
4
|
+
import { isEqual } from "prostgles-types";
|
|
5
|
+
|
|
6
|
+
export const getInferredJoins = (schema: TableSchema[]): Join[] => {
|
|
7
|
+
const joins: Join[] = [];
|
|
8
|
+
const upsertJoin = (
|
|
9
|
+
t1: string,
|
|
10
|
+
t2: string,
|
|
11
|
+
cols: { col1: string; col2: string }[],
|
|
12
|
+
type: Join["type"],
|
|
13
|
+
) => {
|
|
14
|
+
const existingIdx = joins.findIndex((j) => sortedArraysMatch(j.tables, [t1, t2]));
|
|
15
|
+
const existing = joins[existingIdx];
|
|
16
|
+
const normalCond = cols.reduce((a, v) => ({ ...a, [v.col1]: v.col2 }), {});
|
|
17
|
+
const revertedCond = cols.reduce((a, v) => ({ ...a, [v.col2]: v.col1 }), {});
|
|
18
|
+
if (existing) {
|
|
19
|
+
const isLTR = existing.tables[0] === t1;
|
|
20
|
+
const cond = isLTR ? normalCond : revertedCond;
|
|
21
|
+
|
|
22
|
+
/** At some point we should add relationship type to EACH JOIN CONDITION GROUP */
|
|
23
|
+
// const fixedType = isLTR? type : type.split("").reverse().join("") as Join["type"];
|
|
24
|
+
|
|
25
|
+
/** Avoid duplicates */
|
|
26
|
+
if (!existing.on.some((existingOnCondition) => isEqual(existingOnCondition, cond))) {
|
|
27
|
+
existing.on.push(cond);
|
|
28
|
+
joins[existingIdx] = existing;
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
joins.push({
|
|
32
|
+
tables: [t1, t2],
|
|
33
|
+
on: [normalCond],
|
|
34
|
+
type,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
schema.map((tov) => {
|
|
39
|
+
tov.columns.map((col) => {
|
|
40
|
+
if (col.references) {
|
|
41
|
+
col.references.forEach((r) => {
|
|
42
|
+
const joinCols = r.cols.map((c, i) => ({
|
|
43
|
+
col1: c,
|
|
44
|
+
col2: r.fcols[i]!,
|
|
45
|
+
}));
|
|
46
|
+
let type: Join["type"] = "one-many";
|
|
47
|
+
const ftablePKeys = schema
|
|
48
|
+
.find(({ name }) => name === r.ftable)
|
|
49
|
+
?.columns.filter((fcol) => fcol.is_pkey);
|
|
50
|
+
if (ftablePKeys?.length && ftablePKeys.every((fkey) => r.fcols.includes(fkey.name))) {
|
|
51
|
+
type = "one-one";
|
|
52
|
+
}
|
|
53
|
+
upsertJoin(tov.name, r.ftable, joinCols, type);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
return joins;
|
|
59
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import type { DboBuilder } from "../DboBuilder";
|
|
4
|
+
import { prepareShortestJoinPaths } from "./prepareShortestJoinPaths";
|
|
5
|
+
import type { Join } from "../../ProstglesTypes";
|
|
6
|
+
|
|
7
|
+
void test("duplicate configured table pairs are rejected even during table configuration", () => {
|
|
8
|
+
const join: Join = {
|
|
9
|
+
tables: ["conditions", "members"],
|
|
10
|
+
on: [{ project_id: "project_id" }],
|
|
11
|
+
type: "many-many",
|
|
12
|
+
};
|
|
13
|
+
for (const preparingTableConfig of [false, true]) {
|
|
14
|
+
for (const override of [false, true]) {
|
|
15
|
+
for (const tables of [join.tables, [...join.tables].reverse() as Join["tables"]]) {
|
|
16
|
+
const builder = {
|
|
17
|
+
prostgles: {
|
|
18
|
+
preparingTableConfig,
|
|
19
|
+
opts: { joins: [join, { ...join, tables, override }] },
|
|
20
|
+
},
|
|
21
|
+
tablesOrViews: [],
|
|
22
|
+
} as unknown as DboBuilder;
|
|
23
|
+
assert.throws(
|
|
24
|
+
() => prepareShortestJoinPaths(builder),
|
|
25
|
+
/Duplicate configured join for table pair/,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
void test("configured joins are deferred only during table configuration", () => {
|
|
33
|
+
const builder = {
|
|
34
|
+
prostgles: {
|
|
35
|
+
preparingTableConfig: true,
|
|
36
|
+
opts: {
|
|
37
|
+
joins: [
|
|
38
|
+
{
|
|
39
|
+
tables: ["conditions", "members"],
|
|
40
|
+
on: [{ project_id: "project_id" }],
|
|
41
|
+
type: "many-many",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
tablesOrViews: [],
|
|
47
|
+
} as unknown as DboBuilder;
|
|
48
|
+
assert.deepEqual(prepareShortestJoinPaths(builder).joins, []);
|
|
49
|
+
builder.prostgles.preparingTableConfig = false;
|
|
50
|
+
assert.throws(
|
|
51
|
+
() => prepareShortestJoinPaths(builder),
|
|
52
|
+
(error: unknown) => typeof error === "string" && error.includes("Table not found: conditions"),
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
void test("existing configured joins remain available during table configuration", () => {
|
|
57
|
+
const join = {
|
|
58
|
+
tables: ["conditions", "members"],
|
|
59
|
+
on: [{ project_id: "project_id" }],
|
|
60
|
+
type: "many-many",
|
|
61
|
+
};
|
|
62
|
+
const builder = {
|
|
63
|
+
prostgles: { preparingTableConfig: true, opts: { joins: [join] } },
|
|
64
|
+
tablesOrViews: ["conditions", "members"].map((name) => ({
|
|
65
|
+
name,
|
|
66
|
+
columns: [{ name: "project_id" }],
|
|
67
|
+
})),
|
|
68
|
+
} as unknown as DboBuilder;
|
|
69
|
+
assert.deepEqual(prepareShortestJoinPaths(builder).joins, [join]);
|
|
70
|
+
builder.prostgles.preparingTableConfig = false;
|
|
71
|
+
assert.deepEqual(prepareShortestJoinPaths(builder).joins, [join]);
|
|
72
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { isEqual, reverseJoinOn } from "prostgles-types";
|
|
2
|
+
import type { DboBuilder } from "../DboBuilder";
|
|
3
|
+
import type { Join } from "../../ProstglesTypes";
|
|
4
|
+
import { JOIN_TYPES } from "../../ProstglesTypes";
|
|
5
|
+
import type { Graph } from "./shortestPath";
|
|
6
|
+
import { findShortestPath } from "./shortestPath";
|
|
7
|
+
import type { JoinPaths } from "../ViewHandler/ViewHandler";
|
|
8
|
+
import { getInferredJoins } from "./getInferredJoins";
|
|
9
|
+
import { sortedArraysMatch } from "../../utils/utils";
|
|
10
|
+
|
|
11
|
+
type Result = {
|
|
12
|
+
joinGraph?: Graph | undefined;
|
|
13
|
+
joins: Join[];
|
|
14
|
+
shortestJoinPaths: JoinPaths;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const prepareShortestJoinPaths = (dboBuilder: DboBuilder): Result => {
|
|
18
|
+
if (!dboBuilder.prostgles.opts.joins) {
|
|
19
|
+
return {
|
|
20
|
+
joins: [],
|
|
21
|
+
shortestJoinPaths: [],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let joinConfig = dboBuilder.prostgles.opts.joins;
|
|
26
|
+
if (Array.isArray(joinConfig)) {
|
|
27
|
+
joinConfig.forEach((join, index, configured) => {
|
|
28
|
+
const duplicate = configured
|
|
29
|
+
.slice(0, index)
|
|
30
|
+
.some((existing) => isEqual(existing.tables.toSorted(), join.tables.toSorted()));
|
|
31
|
+
if (duplicate) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Duplicate configured join for table pair ${JSON.stringify(join.tables)}. Combine conditions in a single Join.on.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (!dboBuilder.tablesOrViews) {
|
|
39
|
+
throw new Error("Could not create join config. this.tablesOrViews missing");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The initial DBO is needed by tableConfig before its tables/columns exist.
|
|
43
|
+
// Defer only joins that cannot be resolved yet; the final rebuild validates all.
|
|
44
|
+
if (Array.isArray(joinConfig) && dboBuilder.prostgles.preparingTableConfig) {
|
|
45
|
+
const tables = dboBuilder.tablesOrViews;
|
|
46
|
+
joinConfig = joinConfig.filter((join) =>
|
|
47
|
+
join.tables.every((name, index) => {
|
|
48
|
+
const table = tables.find((candidate) => candidate.name === name);
|
|
49
|
+
return (
|
|
50
|
+
table &&
|
|
51
|
+
join.on.every((on) =>
|
|
52
|
+
(index === 0 ? Object.keys(on) : Object.values(on)).every((column) =>
|
|
53
|
+
table.columns.some((candidate) => candidate.name === column),
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const inferredJoins = getInferredJoins(dboBuilder.tablesOrViews);
|
|
61
|
+
if (joinConfig === "inferred") {
|
|
62
|
+
joinConfig = inferredJoins;
|
|
63
|
+
} else if (Array.isArray(joinConfig)) {
|
|
64
|
+
joinConfig = mergeJoins(inferredJoins, joinConfig);
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
66
|
+
} else if (joinConfig) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Unexpected joins init param. Expecting 'inferred' OR joinConfig but got: " +
|
|
69
|
+
JSON.stringify(joinConfig),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const joins = JSON.parse(JSON.stringify(joinConfig)) as Join[];
|
|
73
|
+
|
|
74
|
+
// Validate joins
|
|
75
|
+
try {
|
|
76
|
+
const tovNames = dboBuilder.tablesOrViews.map((t) => t.name);
|
|
77
|
+
|
|
78
|
+
// 2 find incorrect tables
|
|
79
|
+
const missing = joins.flatMap((j) => j.tables).find((t) => !tovNames.includes(t));
|
|
80
|
+
if (missing) {
|
|
81
|
+
throw "Table not found: " + missing;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 3 find incorrect fields
|
|
85
|
+
joins.map(({ tables, on }) => {
|
|
86
|
+
const t1 = tables[0],
|
|
87
|
+
t2 = tables[1];
|
|
88
|
+
on.map((cond) => {
|
|
89
|
+
const f1s = Object.keys(cond),
|
|
90
|
+
f2s = Object.values(cond);
|
|
91
|
+
[
|
|
92
|
+
[t1, f1s],
|
|
93
|
+
[t2, f2s],
|
|
94
|
+
].map((v) => {
|
|
95
|
+
const t = <string>v[0],
|
|
96
|
+
f = <string[]>v[1];
|
|
97
|
+
|
|
98
|
+
const tov = dboBuilder.tablesOrViews!.find((_t) => _t.name === t);
|
|
99
|
+
if (!tov) throw "Table not found: " + t;
|
|
100
|
+
const m1 = f.filter((k) => !tov.columns.map((c) => c.name).includes(k));
|
|
101
|
+
if (m1.length) {
|
|
102
|
+
throw `Table ${t}(${tov.columns.map((c) => c.name).join()}) has no fields named: ${m1.join()}`;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// 4 find incorrect/missing join types
|
|
109
|
+
const expected_types =
|
|
110
|
+
" \n\n-> Expecting: " + JOIN_TYPES.map((t) => JSON.stringify(t)).join(` | `);
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
112
|
+
const mt = joins.find((j) => !j.type);
|
|
113
|
+
if (mt) throw "Join type missing for: " + JSON.stringify(mt, null, 2) + expected_types;
|
|
114
|
+
|
|
115
|
+
const it = joins.find((j) => !JOIN_TYPES.includes(j.type));
|
|
116
|
+
if (it) throw "Incorrect join type for: " + JSON.stringify(it, null, 2) + expected_types;
|
|
117
|
+
} catch (e: any) {
|
|
118
|
+
const errMsg =
|
|
119
|
+
((joinConfig as any) === "inferred" ? "INFERRED " : "") + "JOINS VALIDATION ERROR \n-> " + e;
|
|
120
|
+
throw errMsg;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Make joins graph
|
|
124
|
+
const joinGraph: Graph = {};
|
|
125
|
+
joins.forEach(({ tables }) => {
|
|
126
|
+
const _t = tables.slice().sort(),
|
|
127
|
+
t1 = _t[0]!,
|
|
128
|
+
t2 = _t[1]!;
|
|
129
|
+
|
|
130
|
+
if (t1 === t2) return;
|
|
131
|
+
|
|
132
|
+
joinGraph[t1] ??= {};
|
|
133
|
+
joinGraph[t1][t2] = 1;
|
|
134
|
+
|
|
135
|
+
joinGraph[t2] ??= {};
|
|
136
|
+
joinGraph[t2][t1] = 1;
|
|
137
|
+
});
|
|
138
|
+
const tables = Array.from(new Set(joins.flatMap((t) => t.tables)));
|
|
139
|
+
const shortestJoinPaths: JoinPaths = [];
|
|
140
|
+
tables.forEach((t1) => {
|
|
141
|
+
tables.forEach((t2) => {
|
|
142
|
+
/** Prevent recursion */
|
|
143
|
+
if (
|
|
144
|
+
t1 === t2 ||
|
|
145
|
+
shortestJoinPaths.some((jp) => {
|
|
146
|
+
if (sortedArraysMatch([jp.t1, jp.t2], [t1, t2])) {
|
|
147
|
+
const shortestPath = findShortestPath(joinGraph, t1, t2);
|
|
148
|
+
if (sortedArraysMatch(shortestPath.path, jp.path)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const calculatedPath = findShortestPath(joinGraph, t1, t2);
|
|
158
|
+
if (!(calculatedPath.distance < Infinity)) return;
|
|
159
|
+
|
|
160
|
+
const existing1 = shortestJoinPaths.find((j) => j.t1 === t1 && j.t2 === t2);
|
|
161
|
+
if (!existing1) {
|
|
162
|
+
shortestJoinPaths.push({ t1, t2, path: calculatedPath.path.slice() });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const existing2 = shortestJoinPaths.find((j) => j.t2 === t1 && j.t1 === t2);
|
|
166
|
+
if (!existing2) {
|
|
167
|
+
shortestJoinPaths.push({
|
|
168
|
+
t1: t2,
|
|
169
|
+
t2: t1,
|
|
170
|
+
path: calculatedPath.path.slice().reverse(),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
joins,
|
|
177
|
+
shortestJoinPaths,
|
|
178
|
+
joinGraph,
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const mergeJoins = (inferred: Join[], configured: Join[]): Join[] => {
|
|
183
|
+
const joins = [...inferred];
|
|
184
|
+
configured.forEach((join) => {
|
|
185
|
+
const index = joins.findIndex((existing) =>
|
|
186
|
+
isEqual(existing.tables.toSorted(), join.tables.toSorted()),
|
|
187
|
+
);
|
|
188
|
+
const existing = joins[index];
|
|
189
|
+
const previousOn =
|
|
190
|
+
!existing || join.override ? []
|
|
191
|
+
: existing.tables[0] === join.tables[0] ? existing.on
|
|
192
|
+
: reverseJoinOn(existing.on);
|
|
193
|
+
const on = [...previousOn, ...join.on].filter(
|
|
194
|
+
(condition, index, conditions) =>
|
|
195
|
+
conditions.findIndex((candidate) => isEqual(candidate, condition)) === index,
|
|
196
|
+
);
|
|
197
|
+
const merged = { ...join, on };
|
|
198
|
+
if (existing) {
|
|
199
|
+
joins[index] = merged;
|
|
200
|
+
} else {
|
|
201
|
+
joins.push(merged);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return joins;
|
|
205
|
+
};
|
|
@@ -2,17 +2,6 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
import { AnyObject } from "prostgles-types";
|
|
4
4
|
|
|
5
|
-
const shortestDistanceNode = (distances: Graph, visited: Graph) => {
|
|
6
|
-
let shortest = null;
|
|
7
|
-
|
|
8
|
-
for (const node in distances) {
|
|
9
|
-
const currentIsShortest = shortest === null || distances[node] < distances[shortest];
|
|
10
|
-
if (currentIsShortest && !visited.includes(node)) {
|
|
11
|
-
shortest = node;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return shortest;
|
|
15
|
-
};
|
|
16
5
|
export type Graph = {
|
|
17
6
|
[key: string]: { [key: string]: number };
|
|
18
7
|
};
|
|
@@ -20,7 +9,7 @@ export type Graph = {
|
|
|
20
9
|
export const findShortestPath = (
|
|
21
10
|
graph: Graph,
|
|
22
11
|
startNode: string,
|
|
23
|
-
endNode: string
|
|
12
|
+
endNode: string,
|
|
24
13
|
): { distance: number; path: string[] } => {
|
|
25
14
|
// establish object for recording distances from the start node
|
|
26
15
|
let distances: AnyObject = {};
|
|
@@ -87,7 +76,19 @@ export const findShortestPath = (
|
|
|
87
76
|
return results;
|
|
88
77
|
};
|
|
89
78
|
|
|
90
|
-
|
|
79
|
+
const shortestDistanceNode = (distances: Graph, visited: Graph) => {
|
|
80
|
+
let shortest = null;
|
|
81
|
+
|
|
82
|
+
for (const node in distances) {
|
|
83
|
+
const currentIsShortest = shortest === null || distances[node] < distances[shortest];
|
|
84
|
+
if (currentIsShortest && !visited.includes(node)) {
|
|
85
|
+
shortest = node;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return shortest;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/* Example:
|
|
91
92
|
|
|
92
93
|
const graph = {
|
|
93
94
|
start: { A: 5, B: 2 },
|
package/lib/ProstglesTypes.ts
CHANGED
|
@@ -94,6 +94,8 @@ export type Join = {
|
|
|
94
94
|
tables: [string, string];
|
|
95
95
|
on: { [key: string]: string }[]; // Allow multi references to table
|
|
96
96
|
type: (typeof JOIN_TYPES)[number];
|
|
97
|
+
/** Replace inferred joins for this table pair. Defaults to false. */
|
|
98
|
+
override?: boolean;
|
|
97
99
|
};
|
|
98
100
|
type Joins = Join[] | "inferred";
|
|
99
101
|
|
|
@@ -203,9 +205,8 @@ export type ProstglesInitOptions<
|
|
|
203
205
|
testRulesOnConnect?: boolean;
|
|
204
206
|
|
|
205
207
|
/**
|
|
206
|
-
* Allows defining table relationships that can then be used in filters and data inserts
|
|
207
|
-
*
|
|
208
|
-
* - `Join[]` - specifies the joins manually
|
|
208
|
+
* Allows defining custom table relationships that can then be used in filters and data inserts.
|
|
209
|
+
* By default FKs are used to infer the joins.
|
|
209
210
|
*/
|
|
210
211
|
joins?: Joins;
|
|
211
212
|
|
package/lib/utils/utils.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isEqual } from "prostgles-types";
|
|
2
|
+
|
|
1
3
|
export { get } from "prostgles-types";
|
|
2
4
|
export const clone = <T extends any[] | Record<string, any>>(obj: T): T => {
|
|
3
5
|
if (typeof structuredClone !== "undefined") {
|
|
@@ -27,3 +29,7 @@ const canBeUsedAsIsInTypescript = (str: string): boolean => {
|
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export const isArray = <T>(data: T): data is Extract<T, readonly unknown[]> => Array.isArray(data);
|
|
32
|
+
|
|
33
|
+
export const sortedArraysMatch = (a: string[], b: string[]): boolean => {
|
|
34
|
+
return isEqual(a.toSorted(), b.toSorted());
|
|
35
|
+
};
|
package/package.json
CHANGED
package/tsconfig.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { DboBuilder } from "../DboBuilder/DboBuilder";
|
|
2
|
-
import type { Join } from "../ProstglesTypes";
|
|
3
|
-
import type { Graph } from "../shortestPath";
|
|
4
|
-
import type { JoinPaths } from "./ViewHandler/ViewHandler";
|
|
5
|
-
type Result = {
|
|
6
|
-
joinGraph?: Graph | undefined;
|
|
7
|
-
joins: Join[];
|
|
8
|
-
shortestJoinPaths: JoinPaths;
|
|
9
|
-
};
|
|
10
|
-
export declare function prepareShortestJoinPaths(dboBuilder: DboBuilder): Promise<Result>;
|
|
11
|
-
export {};
|
|
12
|
-
//# sourceMappingURL=prepareShortestJoinPaths.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prepareShortestJoinPaths.d.ts","sourceRoot":"","sources":["../../lib/DboBuilder/prepareShortestJoinPaths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,KAAK,EAAE,KAAK,EAAC,MAAM,iBAAiB,CAAC;AAG5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3D,KAAK,MAAM,GAAG;IACZ,SAAS,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,iBAAiB,EAAE,SAAS,CAAC;CAC9B,CAAC;AACF,wBAAsB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwJtF"}
|