@nshiab/simple-data-analysis-core 2.1.6 → 2.1.8
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/esm/class/SimpleTable.d.ts +399 -90
- package/esm/class/SimpleTable.d.ts.map +1 -1
- package/esm/class/SimpleTable.js +393 -90
- package/esm/helpers/buildGraphTemporalCostStateSql.d.ts +1 -1
- package/esm/helpers/buildGraphTemporalCostStateSql.d.ts.map +1 -1
- package/esm/helpers/buildGraphTemporalCostStateSql.js +23 -9
- package/esm/helpers/graphRouteResultSchema.d.ts +1 -1
- package/esm/helpers/graphRouteResultSchema.d.ts.map +1 -1
- package/esm/helpers/graphRouteResultSchema.js +4 -3
- package/esm/helpers/graphRouteResultSelect.d.ts +1 -1
- package/esm/helpers/graphRouteResultSelect.d.ts.map +1 -1
- package/esm/helpers/graphRouteResultSelect.js +5 -2
- package/esm/helpers/prepareGraphMetricOptions.d.ts +14 -0
- package/esm/helpers/prepareGraphMetricOptions.d.ts.map +1 -0
- package/esm/helpers/prepareGraphMetricOptions.js +41 -0
- package/esm/helpers/prepareGraphRouteSql.d.ts +2 -2
- package/esm/helpers/prepareGraphRouteSql.d.ts.map +1 -1
- package/esm/helpers/prepareGraphRouteSql.js +4 -4
- package/esm/helpers/prepareGraphTemporalSql.d.ts +3 -0
- package/esm/helpers/prepareGraphTemporalSql.d.ts.map +1 -1
- package/esm/helpers/prepareGraphTemporalSql.js +7 -0
- package/esm/helpers/validateGraphRouteResultSchema.d.ts +1 -1
- package/esm/helpers/validateGraphRouteResultSchema.d.ts.map +1 -1
- package/esm/helpers/validateGraphRouteResultSchema.js +3 -1
- package/esm/methods/distances.d.ts +2 -0
- package/esm/methods/distances.d.ts.map +1 -1
- package/esm/methods/distances.js +274 -20
- package/esm/methods/findCycles.d.ts +1 -0
- package/esm/methods/findCycles.d.ts.map +1 -1
- package/esm/methods/findCycles.js +22 -15
- package/esm/methods/paths.d.ts +1 -0
- package/esm/methods/paths.d.ts.map +1 -1
- package/esm/methods/paths.js +21 -14
- package/esm/methods/shortestPath.d.ts +2 -0
- package/esm/methods/shortestPath.d.ts.map +1 -1
- package/esm/methods/shortestPath.js +155 -34
- package/llm.md +428 -108
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/script/class/SimpleTable.d.ts +399 -90
- package/script/class/SimpleTable.d.ts.map +1 -1
- package/script/class/SimpleTable.js +393 -90
- package/script/helpers/buildGraphTemporalCostStateSql.d.ts +1 -1
- package/script/helpers/buildGraphTemporalCostStateSql.d.ts.map +1 -1
- package/script/helpers/buildGraphTemporalCostStateSql.js +23 -9
- package/script/helpers/graphRouteResultSchema.d.ts +1 -1
- package/script/helpers/graphRouteResultSchema.d.ts.map +1 -1
- package/script/helpers/graphRouteResultSchema.js +4 -3
- package/script/helpers/graphRouteResultSelect.d.ts +1 -1
- package/script/helpers/graphRouteResultSelect.d.ts.map +1 -1
- package/script/helpers/graphRouteResultSelect.js +5 -2
- package/script/helpers/prepareGraphMetricOptions.d.ts +14 -0
- package/script/helpers/prepareGraphMetricOptions.d.ts.map +1 -0
- package/script/helpers/prepareGraphMetricOptions.js +44 -0
- package/script/helpers/prepareGraphRouteSql.d.ts +2 -2
- package/script/helpers/prepareGraphRouteSql.d.ts.map +1 -1
- package/script/helpers/prepareGraphRouteSql.js +4 -4
- package/script/helpers/prepareGraphTemporalSql.d.ts +3 -0
- package/script/helpers/prepareGraphTemporalSql.d.ts.map +1 -1
- package/script/helpers/prepareGraphTemporalSql.js +7 -0
- package/script/helpers/validateGraphRouteResultSchema.d.ts +1 -1
- package/script/helpers/validateGraphRouteResultSchema.d.ts.map +1 -1
- package/script/helpers/validateGraphRouteResultSchema.js +3 -1
- package/script/methods/distances.d.ts +2 -0
- package/script/methods/distances.d.ts.map +1 -1
- package/script/methods/distances.js +274 -20
- package/script/methods/findCycles.d.ts +1 -0
- package/script/methods/findCycles.d.ts.map +1 -1
- package/script/methods/findCycles.js +22 -15
- package/script/methods/paths.d.ts +1 -0
- package/script/methods/paths.d.ts.map +1 -1
- package/script/methods/paths.js +21 -14
- package/script/methods/shortestPath.d.ts +2 -0
- package/script/methods/shortestPath.d.ts.map +1 -1
- package/script/methods/shortestPath.js +155 -34
package/esm/methods/paths.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import prepareGraphMetricOptions from "../helpers/prepareGraphMetricOptions.js";
|
|
1
2
|
import { prepareGraphRouteEndpoints, prepareGraphRouteSql, validateGraphRouteInputs, } from "../helpers/prepareGraphRouteSql.js";
|
|
2
3
|
import graphRouteResultSchema from "../helpers/graphRouteResultSchema.js";
|
|
3
4
|
import graphRouteResultSelect from "../helpers/graphRouteResultSelect.js";
|
|
@@ -30,6 +31,7 @@ export default function paths(simpleTable, sourceColumn, targetColumn, edgeId, s
|
|
|
30
31
|
typeof options.outputTable !== "boolean") {
|
|
31
32
|
throw new TypeError("paths() options.outputTable must be a string or boolean.");
|
|
32
33
|
}
|
|
34
|
+
const metric = prepareGraphMetricOptions(options, "paths()", false);
|
|
33
35
|
const endpoints = prepareGraphRouteEndpoints(start, end, "paths()");
|
|
34
36
|
const direction = options.direction ?? "outgoing";
|
|
35
37
|
const temporalOptions = prepareGraphTemporalOptions(options, direction, "paths()");
|
|
@@ -50,7 +52,8 @@ export default function paths(simpleTable, sourceColumn, targetColumn, edgeId, s
|
|
|
50
52
|
? undefined
|
|
51
53
|
: (input) => validateGraphTemporalEvents(input, temporalOptions, "paths()", parameters),
|
|
52
54
|
values: (schema) => {
|
|
53
|
-
validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "paths()");
|
|
55
|
+
const validated = validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "paths()");
|
|
56
|
+
graphRouteResultSchema(schema, validated.distanceType, "paths()", metric.elapsedTime);
|
|
54
57
|
const temporal = temporalOptions === undefined
|
|
55
58
|
? undefined
|
|
56
59
|
: prepareGraphTemporalSql(schema, temporalOptions, "paths()");
|
|
@@ -58,22 +61,22 @@ export default function paths(simpleTable, sourceColumn, targetColumn, edgeId, s
|
|
|
58
61
|
? endpoints.values.values
|
|
59
62
|
: [temporal.gapParameter, ...endpoints.values.values];
|
|
60
63
|
},
|
|
61
|
-
buildSelect: (input, schema) => pathsSelect(input, schema, sourceColumn, targetColumn, edgeId, endpoints, direction, options.weight, temporalOptions),
|
|
64
|
+
buildSelect: (input, schema) => pathsSelect(input, schema, sourceColumn, targetColumn, edgeId, endpoints, direction, options.weight, temporalOptions, metric.elapsedTime),
|
|
62
65
|
outputSchema: (schema) => {
|
|
63
66
|
const validated = validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "paths()");
|
|
64
67
|
if (temporalOptions !== undefined) {
|
|
65
68
|
prepareGraphTemporalSql(schema, temporalOptions, "paths()");
|
|
66
69
|
}
|
|
67
|
-
return graphRouteResultSchema(schema, validated.distanceType, "paths()");
|
|
70
|
+
return graphRouteResultSchema(schema, validated.distanceType, "paths()", metric.elapsedTime);
|
|
68
71
|
},
|
|
69
72
|
});
|
|
70
73
|
}
|
|
71
|
-
function pathsSelect(input, schema, source, target, edgeId, routeEndpoints, direction, weight, temporalOptions) {
|
|
74
|
+
function pathsSelect(input, schema, source, target, edgeId, routeEndpoints, direction, weight, temporalOptions, elapsedTime) {
|
|
72
75
|
const route = prepareGraphRouteSql(input, schema, source, target, edgeId, routeEndpoints, weight, "paths()");
|
|
73
76
|
const prepared = route.traversal;
|
|
74
77
|
if (temporalOptions !== undefined) {
|
|
75
78
|
const temporal = prepareGraphTemporalSql(schema, temporalOptions, "paths()");
|
|
76
|
-
return temporalPathsSelect(route, direction, temporal);
|
|
79
|
+
return temporalPathsSelect(route, direction, temporal, elapsedTime);
|
|
77
80
|
}
|
|
78
81
|
const distanceType = route.distanceType;
|
|
79
82
|
const q = quoteIdentifier;
|
|
@@ -166,7 +169,7 @@ function pathsSelect(input, schema, source, target, edgeId, routeEndpoints, dire
|
|
|
166
169
|
)
|
|
167
170
|
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn)}`;
|
|
168
171
|
}
|
|
169
|
-
function temporalPathsSelect(route, direction, temporal) {
|
|
172
|
+
function temporalPathsSelect(route, direction, temporal, elapsedTime) {
|
|
170
173
|
const prepared = route.traversal;
|
|
171
174
|
const distanceType = route.distanceType;
|
|
172
175
|
const q = quoteIdentifier;
|
|
@@ -197,12 +200,16 @@ function temporalPathsSelect(route, direction, temporal) {
|
|
|
197
200
|
const edgeKey = `${q("edges")}.${q("__edge_key")}`;
|
|
198
201
|
const candidateDistance = `CAST(${q("routes")}.${q("distance")} + ${edgeCost} AS ${distanceType})`;
|
|
199
202
|
const gap = `${q("settings")}.${q("__gap")}`;
|
|
200
|
-
const
|
|
203
|
+
const firstAnchor = temporal.journeyAnchor("edges", direction);
|
|
204
|
+
const anchor = `${q("routes")}.${q("__journey_anchor")}`;
|
|
205
|
+
const step = (distance, journeyAnchor) => `struct_pack(
|
|
201
206
|
${q("edgeId")} := ${q("edges")}.${q("__edge_id")},
|
|
202
207
|
${q("source")} := ${q("edges")}.${q("__from")},
|
|
203
208
|
${q("target")} := ${edgeTo},
|
|
204
209
|
${q("weight")} := ${edgeCost},
|
|
205
|
-
${q("distance")} := ${distance}
|
|
210
|
+
${q("distance")} := ${distance}${elapsedTime
|
|
211
|
+
? `, ${q("elapsedTimeMs")} := ${temporal.elapsedMilliseconds(temporal.journeyElapsed("edges", journeyAnchor, direction))}`
|
|
212
|
+
: ""}
|
|
206
213
|
)`;
|
|
207
214
|
return `WITH RECURSIVE ${settingsRelation} AS MATERIALIZED (
|
|
208
215
|
SELECT CAST(? AS HUGEINT) AS ${q("__gap")}
|
|
@@ -241,14 +248,14 @@ function temporalPathsSelect(route, direction, temporal) {
|
|
|
241
248
|
), ${routesRelation}(
|
|
242
249
|
${q("node")}, ${q("__node_key")}, ${q("__visited")},
|
|
243
250
|
${q("__edge_keys")}, ${q("steps")}, ${q("distance")},
|
|
244
|
-
${q("__event_id")}, ${q("__event_start")}, ${q("__event_end")}
|
|
251
|
+
${q("__event_id")}, ${q("__event_start")}, ${q("__event_end")}${elapsedTime ? `, ${q("__journey_anchor")}` : ""}
|
|
245
252
|
) AS (
|
|
246
253
|
SELECT ${edgeTo}, ${edgeToKey},
|
|
247
254
|
[${q("__start_key")}, ${edgeToKey}], [${edgeKey}],
|
|
248
|
-
[${step(edgeCost)}], ${edgeCost},
|
|
255
|
+
[${step(edgeCost, firstAnchor)}], ${edgeCost},
|
|
249
256
|
${q("edges")}.${q("__event_id")},
|
|
250
257
|
${q("edges")}.${q("__event_start")},
|
|
251
|
-
${q("edges")}.${q("__event_end")}
|
|
258
|
+
${q("edges")}.${q("__event_end")}${elapsedTime ? `, ${firstAnchor}` : ""}
|
|
252
259
|
FROM ${endpointRelation}
|
|
253
260
|
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
254
261
|
ON ${q("__start_key")} = ${edgeFromKey}
|
|
@@ -259,10 +266,10 @@ function temporalPathsSelect(route, direction, temporal) {
|
|
|
259
266
|
SELECT ${edgeTo}, ${edgeToKey},
|
|
260
267
|
list_append(${q("routes")}.${q("__visited")}, ${edgeToKey}),
|
|
261
268
|
list_append(${q("routes")}.${q("__edge_keys")}, ${edgeKey}),
|
|
262
|
-
list_append(${q("routes")}.${q("steps")}, ${step(candidateDistance)}),
|
|
269
|
+
list_append(${q("routes")}.${q("steps")}, ${step(candidateDistance, anchor)}),
|
|
263
270
|
${candidateDistance}, ${q("edges")}.${q("__event_id")},
|
|
264
271
|
${q("edges")}.${q("__event_start")},
|
|
265
|
-
${q("edges")}.${q("__event_end")}
|
|
272
|
+
${q("edges")}.${q("__event_end")}${elapsedTime ? `, ${anchor}` : ""}
|
|
266
273
|
FROM ${routesRelation} AS ${q("routes")}
|
|
267
274
|
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
268
275
|
ON ${q("routes")}.${q("__node_key")} = ${edgeFromKey}
|
|
@@ -283,5 +290,5 @@ function temporalPathsSelect(route, direction, temporal) {
|
|
|
283
290
|
AS ${q("pathId")}, ${q("steps")}
|
|
284
291
|
FROM ${completeRelation}
|
|
285
292
|
)
|
|
286
|
-
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn)}`;
|
|
293
|
+
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn, false, elapsedTime)}`;
|
|
287
294
|
}
|
|
@@ -6,6 +6,8 @@ type ShortestPathOptions = GraphTemporalOptions & {
|
|
|
6
6
|
direction?: GraphDirection;
|
|
7
7
|
outputTable?: string | boolean;
|
|
8
8
|
weight?: string;
|
|
9
|
+
elapsedTime?: boolean;
|
|
10
|
+
minimize?: "steps" | "weight" | "elapsedTime";
|
|
9
11
|
};
|
|
10
12
|
export default function shortestPath(simpleTable: SimpleTable, sourceColumn: string, targetColumn: string, edgeId: string, start: GraphId, end: GraphId, options?: ShortestPathOptions): SimpleTable;
|
|
11
13
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shortestPath.d.ts","sourceRoot":"","sources":["../../src/methods/shortestPath.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"shortestPath.d.ts","sourceRoot":"","sources":["../../src/methods/shortestPath.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAMvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAShE,OAAgC,EAC9B,KAAK,oBAAoB,EAG1B,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAK1E,KAAK,mBAAmB,GAAG,oBAAoB,GAAG;IAChD,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,aAAa,CAAC;CAC/C,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,YAAY,CAClC,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,mBAAwB,GAChC,WAAW,CAyHb"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import buildGraphTemporalCostStateSql from "../helpers/buildGraphTemporalCostStateSql.js";
|
|
2
|
+
import prepareGraphMetricOptions from "../helpers/prepareGraphMetricOptions.js";
|
|
2
3
|
import { prepareGraphRouteEndpoints, prepareGraphRouteSql, validateGraphRouteInputs, } from "../helpers/prepareGraphRouteSql.js";
|
|
3
4
|
import graphRouteResultSchema from "../helpers/graphRouteResultSchema.js";
|
|
4
5
|
import graphRouteResultSelect from "../helpers/graphRouteResultSelect.js";
|
|
@@ -31,6 +32,7 @@ export default function shortestPath(simpleTable, sourceColumn, targetColumn, ed
|
|
|
31
32
|
typeof options.outputTable !== "boolean") {
|
|
32
33
|
throw new TypeError("shortestPath() options.outputTable must be a string or boolean.");
|
|
33
34
|
}
|
|
35
|
+
const metrics = prepareGraphMetricOptions(options, "shortestPath()", true);
|
|
34
36
|
const endpoints = prepareGraphRouteEndpoints(start, end, "shortestPath()");
|
|
35
37
|
const direction = options.direction ?? "outgoing";
|
|
36
38
|
const temporalOptions = prepareGraphTemporalOptions(options, direction, "shortestPath()");
|
|
@@ -51,7 +53,7 @@ export default function shortestPath(simpleTable, sourceColumn, targetColumn, ed
|
|
|
51
53
|
? undefined
|
|
52
54
|
: (input) => validateGraphTemporalEvents(input, temporalOptions, "shortestPath()", parameters),
|
|
53
55
|
values: (schema) => {
|
|
54
|
-
validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "shortestPath()");
|
|
56
|
+
validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "shortestPath()", options.weight !== undefined);
|
|
55
57
|
const temporal = temporalOptions === undefined
|
|
56
58
|
? undefined
|
|
57
59
|
: prepareGraphTemporalSql(schema, temporalOptions, "shortestPath()");
|
|
@@ -59,29 +61,30 @@ export default function shortestPath(simpleTable, sourceColumn, targetColumn, ed
|
|
|
59
61
|
? endpoints.values.values
|
|
60
62
|
: [temporal.gapParameter, ...endpoints.values.values];
|
|
61
63
|
},
|
|
62
|
-
buildSelect: (input, schema) => shortestPathSelect(input, schema, sourceColumn, targetColumn, edgeId, endpoints, direction, options.weight, temporalOptions),
|
|
64
|
+
buildSelect: (input, schema) => shortestPathSelect(input, schema, sourceColumn, targetColumn, edgeId, endpoints, direction, options.weight, temporalOptions, metrics),
|
|
63
65
|
outputSchema: (schema) => {
|
|
64
|
-
const validated = validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "shortestPath()");
|
|
66
|
+
const validated = validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "shortestPath()", options.weight !== undefined);
|
|
65
67
|
if (temporalOptions !== undefined) {
|
|
66
68
|
prepareGraphTemporalSql(schema, temporalOptions, "shortestPath()");
|
|
67
69
|
}
|
|
68
|
-
return graphRouteResultSchema(schema, validated.distanceType, "shortestPath()");
|
|
70
|
+
return graphRouteResultSchema(schema, validated.distanceType, "shortestPath()", metrics.elapsedTime, options.weight !== undefined);
|
|
69
71
|
},
|
|
70
72
|
});
|
|
71
73
|
}
|
|
72
|
-
function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoints, direction, weight, temporalOptions) {
|
|
73
|
-
const route = prepareGraphRouteSql(input, schema, source, target, edgeId, routeEndpoints, weight, "shortestPath()");
|
|
74
|
+
function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoints, direction, weight, temporalOptions, metrics) {
|
|
75
|
+
const route = prepareGraphRouteSql(input, schema, source, target, edgeId, routeEndpoints, weight, "shortestPath()", weight !== undefined);
|
|
74
76
|
const prepared = route.traversal;
|
|
75
77
|
if (temporalOptions !== undefined) {
|
|
76
78
|
const temporal = prepareGraphTemporalSql(schema, temporalOptions, "shortestPath()");
|
|
77
|
-
return temporalShortestPathSelect(route, direction, temporal);
|
|
79
|
+
return temporalShortestPathSelect(route, direction, temporal, metrics, weight !== undefined);
|
|
78
80
|
}
|
|
79
81
|
const distanceType = route.distanceType;
|
|
80
82
|
// Exact shortest routes have optimal prefixes. Floating addition can erase
|
|
81
83
|
// a prefix-cost difference later, so retain all simple prefixes up to the
|
|
82
84
|
// best complete cost before selecting the full-route ties.
|
|
83
|
-
const floating =
|
|
84
|
-
route.weightType === "DOUBLE";
|
|
85
|
+
const floating = metrics.minimize === "weight" &&
|
|
86
|
+
(route.weightType === "FLOAT" || route.weightType === "DOUBLE");
|
|
87
|
+
const metricType = metrics.minimize === "steps" ? "BIGINT" : distanceType;
|
|
85
88
|
const relations = prepared.relationNames([
|
|
86
89
|
"graph_route_endpoints",
|
|
87
90
|
"graph_edges",
|
|
@@ -107,8 +110,17 @@ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoint
|
|
|
107
110
|
const edgeToKey = `${q("edges")}.${q("__to_key")}`;
|
|
108
111
|
const edgeTo = `${q("edges")}.${q("__to")}`;
|
|
109
112
|
const edgeCost = `${q("edges")}.${q("__weight")}`;
|
|
113
|
+
const edgeMetric = metrics.minimize === "steps"
|
|
114
|
+
? "CAST(1 AS BIGINT)"
|
|
115
|
+
: edgeCost;
|
|
110
116
|
const routeDistance = `${q("routes")}.${q("distance")}`;
|
|
111
117
|
const candidateDistance = `CAST(${routeDistance} + ${edgeCost} AS ${distanceType})`;
|
|
118
|
+
const routeMetric = metrics.minimize === "steps"
|
|
119
|
+
? `len(${q("routes")}.${q("steps")})`
|
|
120
|
+
: routeDistance;
|
|
121
|
+
const candidateMetric = metrics.minimize === "steps"
|
|
122
|
+
? `${routeMetric} + 1`
|
|
123
|
+
: candidateDistance;
|
|
112
124
|
return `WITH RECURSIVE ${endpointRelation}(
|
|
113
125
|
${q("start")}, ${q("end")}, ${q("__start_key")}, ${q("__end_key")}
|
|
114
126
|
) AS (
|
|
@@ -140,13 +152,13 @@ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoint
|
|
|
140
152
|
${q("node")}, ${q("__node_key")}, ${q("distance")}
|
|
141
153
|
) USING KEY(${q("__node_key")}) AS (
|
|
142
154
|
SELECT ${q("start")}, ${q("__start_key")},
|
|
143
|
-
CAST(0 AS ${
|
|
155
|
+
CAST(0 AS ${metricType})
|
|
144
156
|
FROM ${endpointRelation}
|
|
145
157
|
INNER JOIN ${toEndRelation}
|
|
146
158
|
ON ${q("__start_key")} = ${q("__key")}
|
|
147
159
|
UNION
|
|
148
160
|
SELECT ${edgeTo}, ${edgeToKey},
|
|
149
|
-
MIN(CAST(${q("reached")}.${q("distance")} + ${
|
|
161
|
+
MIN(CAST(${q("reached")}.${q("distance")} + ${edgeMetric} AS ${metricType}))
|
|
150
162
|
FROM ${distancesRelation} AS ${q("reached")}
|
|
151
163
|
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
152
164
|
ON ${q("reached")}.${q("__node_key")} = ${edgeFromKey}
|
|
@@ -156,7 +168,7 @@ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoint
|
|
|
156
168
|
ON ${edgeToKey} = ${q("best")}.${q("__node_key")}
|
|
157
169
|
GROUP BY ${edgeTo}, ${edgeToKey}, ${q("best")}.${q("distance")}
|
|
158
170
|
HAVING ${q("best")}.${q("distance")} IS NULL
|
|
159
|
-
OR MIN(CAST(${q("reached")}.${q("distance")} + ${
|
|
171
|
+
OR MIN(CAST(${q("reached")}.${q("distance")} + ${edgeMetric} AS ${metricType})) < ${q("best")}.${q("distance")}
|
|
160
172
|
), ${bestTotalRelation} AS (
|
|
161
173
|
SELECT ${q("distances")}.${q("distance")}
|
|
162
174
|
FROM ${distancesRelation} AS ${q("distances")}
|
|
@@ -195,23 +207,23 @@ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoint
|
|
|
195
207
|
WHERE ${q("routes")}.${q("__node_key")} <> ${q("__end_key")}
|
|
196
208
|
AND NOT list_contains(${q("routes")}.${q("__visited")}, ${edgeToKey})
|
|
197
209
|
AND ${floating
|
|
198
|
-
? `${
|
|
199
|
-
: `${
|
|
210
|
+
? `${candidateMetric} <= ${q("best_total")}.${q("distance")}`
|
|
211
|
+
: `${candidateMetric} = ${q("best_prefix")}.${q("distance")}`}
|
|
200
212
|
), ${shortestRelation} AS (
|
|
201
213
|
SELECT ${q("__edge_keys")}, ${q("steps")}
|
|
202
|
-
FROM ${routesRelation}
|
|
214
|
+
FROM ${routesRelation} AS ${q("routes")}
|
|
203
215
|
CROSS JOIN ${endpointRelation}
|
|
204
216
|
CROSS JOIN ${bestTotalRelation} AS ${q("best_total")}
|
|
205
217
|
WHERE ${q("__node_key")} = ${q("__end_key")}
|
|
206
|
-
AND ${
|
|
218
|
+
AND ${routeMetric} = ${q("best_total")}.${q("distance")}
|
|
207
219
|
), ${rankedRelation} AS (
|
|
208
220
|
SELECT CAST(row_number() OVER (ORDER BY ${q("__edge_keys")}) - 1 AS BIGINT) AS ${q("pathId")},
|
|
209
221
|
${q("steps")}
|
|
210
222
|
FROM ${shortestRelation}
|
|
211
223
|
)
|
|
212
|
-
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn)}`;
|
|
224
|
+
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn, false, false, weight !== undefined)}`;
|
|
213
225
|
}
|
|
214
|
-
function temporalShortestPathSelect(route, direction, temporal) {
|
|
226
|
+
function temporalShortestPathSelect(route, direction, temporal, metrics, includeTotal) {
|
|
215
227
|
const prepared = route.traversal;
|
|
216
228
|
const q = quoteIdentifier;
|
|
217
229
|
const startsSelect = `SELECT ${q("start")},
|
|
@@ -219,10 +231,12 @@ function temporalShortestPathSelect(route, direction, temporal) {
|
|
|
219
231
|
FROM (SELECT ${route.endpointValue} AS ${q("start")}) AS ${q("values")}`;
|
|
220
232
|
// Non-negative weights and no maximum gap let cycles be removed from walks.
|
|
221
233
|
// Their optimum bounds simple routes; retain separate histories for all ties.
|
|
222
|
-
const costs =
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
234
|
+
const costs = metrics.minimize === "elapsedTime"
|
|
235
|
+
? buildElapsedShortestPathCostSql(route, startsSelect, direction, temporal)
|
|
236
|
+
: buildGraphTemporalCostStateSql(prepared, startsSelect, direction, temporal, route.distanceType, route.edgeWeight, [
|
|
237
|
+
`${route.typedEdgeId} AS ${q("__edge_id")}`,
|
|
238
|
+
`${route.edgeKey} AS ${q("__edge_key")}`,
|
|
239
|
+
], metrics.minimize === "steps" ? "steps" : "weight");
|
|
226
240
|
const relations = prepared.relationNames([
|
|
227
241
|
"graph_route_endpoints",
|
|
228
242
|
"graph_nodes",
|
|
@@ -248,12 +262,33 @@ function temporalShortestPathSelect(route, direction, temporal) {
|
|
|
248
262
|
const routeDistance = `${q("routes")}.${q("distance")}`;
|
|
249
263
|
const candidateDistance = `CAST(${routeDistance} + ${edgeCost} AS ${route.distanceType})`;
|
|
250
264
|
const gap = `${q("settings")}.${q("__gap")}`;
|
|
251
|
-
const
|
|
265
|
+
const firstAnchor = temporal.journeyAnchor("edges", direction);
|
|
266
|
+
const routeAnchor = `${q("routes")}.${q("__journey_anchor")}`;
|
|
267
|
+
const firstElapsed = temporal.journeyElapsed("edges", firstAnchor, direction);
|
|
268
|
+
const candidateElapsed = temporal.journeyElapsed("edges", routeAnchor, direction);
|
|
269
|
+
const firstMetric = metrics.minimize === "steps"
|
|
270
|
+
? "1"
|
|
271
|
+
: metrics.minimize === "elapsedTime"
|
|
272
|
+
? firstElapsed
|
|
273
|
+
: edgeCost;
|
|
274
|
+
const routeMetric = metrics.minimize === "steps"
|
|
275
|
+
? `len(${q("routes")}.${q("steps")})`
|
|
276
|
+
: metrics.minimize === "elapsedTime"
|
|
277
|
+
? `${q("routes")}.${q("__elapsed")}`
|
|
278
|
+
: routeDistance;
|
|
279
|
+
const candidateMetric = metrics.minimize === "steps"
|
|
280
|
+
? `${routeMetric} + 1`
|
|
281
|
+
: metrics.minimize === "elapsedTime"
|
|
282
|
+
? candidateElapsed
|
|
283
|
+
: candidateDistance;
|
|
284
|
+
const step = (distance, elapsed) => `struct_pack(
|
|
252
285
|
${q("edgeId")} := ${q("edges")}.${q("__edge_id")},
|
|
253
286
|
${q("source")} := ${q("edges")}.${q("__from")},
|
|
254
287
|
${q("target")} := ${edgeTo},
|
|
255
288
|
${q("weight")} := ${edgeCost},
|
|
256
|
-
${q("distance")} := ${distance}
|
|
289
|
+
${q("distance")} := ${distance}${metrics.elapsedTime
|
|
290
|
+
? `, ${q("elapsedTimeMs")} := ${temporal.elapsedMilliseconds(elapsed)}`
|
|
291
|
+
: ""}
|
|
257
292
|
)`;
|
|
258
293
|
return `${costs.withClause}, ${endpointRelation}(
|
|
259
294
|
${q("start")}, ${q("end")}, ${q("__start_key")}, ${q("__end_key")}
|
|
@@ -278,44 +313,44 @@ function temporalShortestPathSelect(route, direction, temporal) {
|
|
|
278
313
|
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
279
314
|
ON ${q("reached")}.${q("__key")} = ${edgeToKey}
|
|
280
315
|
), ${bestTotalRelation} AS (
|
|
281
|
-
SELECT MIN(${q("costs")}.${q("distance")}) AS ${q("distance")}
|
|
316
|
+
SELECT MIN(${q("costs")}.${q(metrics.minimize === "steps" ? "steps" : "distance")}) AS ${q("distance")}
|
|
282
317
|
FROM ${costs.costRelation} AS ${q("costs")}
|
|
283
318
|
INNER JOIN ${endpointRelation}
|
|
284
319
|
ON ${q("costs")}.${q("__node_key")} = ${q("__end_key")}
|
|
285
320
|
), ${routesRelation}(
|
|
286
321
|
${q("node")}, ${q("__node_key")}, ${q("__visited")},
|
|
287
322
|
${q("__edge_keys")}, ${q("steps")}, ${q("distance")},
|
|
288
|
-
${q("__event_id")}, ${q("__event_start")}, ${q("__event_end")}
|
|
323
|
+
${q("__event_id")}, ${q("__event_start")}, ${q("__event_end")}${metrics.elapsedTime ? `, ${q("__journey_anchor")}, ${q("__elapsed")}` : ""}
|
|
289
324
|
) AS (
|
|
290
325
|
SELECT ${edgeTo}, ${edgeToKey},
|
|
291
326
|
[${q("__start_key")}, ${edgeToKey}], [${edgeKey}],
|
|
292
|
-
[${step(edgeCost)}], ${edgeCost},
|
|
327
|
+
[${step(edgeCost, firstElapsed)}], ${edgeCost},
|
|
293
328
|
${q("edges")}.${q("__event_id")},
|
|
294
329
|
${q("edges")}.${q("__event_start")},
|
|
295
|
-
${q("edges")}.${q("__event_end")}
|
|
330
|
+
${q("edges")}.${q("__event_end")}${metrics.elapsedTime ? `, ${firstAnchor}, ${firstElapsed}` : ""}
|
|
296
331
|
FROM ${endpointRelation}
|
|
297
332
|
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
298
333
|
ON ${q("__start_key")} = ${edgeFromKey}
|
|
299
334
|
INNER JOIN ${toEndRelation} AS ${q("can_reach_end")}
|
|
300
335
|
ON ${edgeToKey} = ${q("can_reach_end")}.${q("__key")}
|
|
301
336
|
INNER JOIN ${bestTotalRelation} AS ${q("best_total")}
|
|
302
|
-
ON ${
|
|
337
|
+
ON ${firstMetric} <= ${q("best_total")}.${q("distance")}
|
|
303
338
|
WHERE ${q("__start_key")} <> ${edgeToKey}
|
|
304
339
|
UNION ALL
|
|
305
340
|
SELECT ${edgeTo}, ${edgeToKey},
|
|
306
341
|
list_append(${q("routes")}.${q("__visited")}, ${edgeToKey}),
|
|
307
342
|
list_append(${q("routes")}.${q("__edge_keys")}, ${edgeKey}),
|
|
308
|
-
list_append(${q("routes")}.${q("steps")}, ${step(candidateDistance)}),
|
|
343
|
+
list_append(${q("routes")}.${q("steps")}, ${step(candidateDistance, candidateElapsed)}),
|
|
309
344
|
${candidateDistance}, ${q("edges")}.${q("__event_id")},
|
|
310
345
|
${q("edges")}.${q("__event_start")},
|
|
311
|
-
${q("edges")}.${q("__event_end")}
|
|
346
|
+
${q("edges")}.${q("__event_end")}${metrics.elapsedTime ? `, ${routeAnchor}, ${candidateElapsed}` : ""}
|
|
312
347
|
FROM ${routesRelation} AS ${q("routes")}
|
|
313
348
|
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
314
349
|
ON ${q("routes")}.${q("__node_key")} = ${edgeFromKey}
|
|
315
350
|
INNER JOIN ${toEndRelation} AS ${q("can_reach_end")}
|
|
316
351
|
ON ${edgeToKey} = ${q("can_reach_end")}.${q("__key")}
|
|
317
352
|
INNER JOIN ${bestTotalRelation} AS ${q("best_total")}
|
|
318
|
-
ON ${
|
|
353
|
+
ON ${candidateMetric} <= ${q("best_total")}.${q("distance")}
|
|
319
354
|
CROSS JOIN ${endpointRelation}
|
|
320
355
|
CROSS JOIN ${costs.settingsRelation} AS ${q("settings")}
|
|
321
356
|
WHERE ${q("routes")}.${q("__node_key")} <> ${q("__end_key")}
|
|
@@ -327,11 +362,97 @@ function temporalShortestPathSelect(route, direction, temporal) {
|
|
|
327
362
|
CROSS JOIN ${endpointRelation}
|
|
328
363
|
CROSS JOIN ${bestTotalRelation} AS ${q("best_total")}
|
|
329
364
|
WHERE ${q("__node_key")} = ${q("__end_key")}
|
|
330
|
-
AND ${
|
|
365
|
+
AND ${routeMetric} = ${q("best_total")}.${q("distance")}
|
|
331
366
|
), ${rankedRelation} AS (
|
|
332
367
|
SELECT CAST(row_number() OVER (ORDER BY ${q("__edge_keys")}) - 1 AS BIGINT)
|
|
333
368
|
AS ${q("pathId")}, ${q("steps")}
|
|
334
369
|
FROM ${shortestRelation}
|
|
335
370
|
)
|
|
336
|
-
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn)}`;
|
|
371
|
+
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn, false, metrics.elapsedTime, includeTotal)}`;
|
|
372
|
+
}
|
|
373
|
+
// At the same physical event a later departure (or earlier incoming arrival)
|
|
374
|
+
// dominates every future elapsed duration. Keyed improvements find the optimum
|
|
375
|
+
// without enumerating route histories; reconstruction still retains all ties.
|
|
376
|
+
function buildElapsedShortestPathCostSql(route, startsSelect, direction, temporal) {
|
|
377
|
+
const prepared = route.traversal;
|
|
378
|
+
const q = quoteIdentifier;
|
|
379
|
+
const relations = prepared.relationNames([
|
|
380
|
+
"graph_starts",
|
|
381
|
+
"graph_temporal_settings",
|
|
382
|
+
"graph_event_rows",
|
|
383
|
+
"graph_edges",
|
|
384
|
+
"graph_cost_states",
|
|
385
|
+
]);
|
|
386
|
+
const startsRelation = relations.graph_starts;
|
|
387
|
+
const settingsRelation = relations.graph_temporal_settings;
|
|
388
|
+
const eventRowsRelation = relations.graph_event_rows;
|
|
389
|
+
const edgesRelation = relations.graph_edges;
|
|
390
|
+
const costRelation = relations.graph_cost_states;
|
|
391
|
+
const start = `${q("starts")}.${q("start")}`;
|
|
392
|
+
const startKey = `${q("starts")}.${q("__key")}`;
|
|
393
|
+
const reachedStart = `${q("reached")}.${q("start")}`;
|
|
394
|
+
const reachedStartKey = `${q("reached")}.${q("__start_key")}`;
|
|
395
|
+
const reachedNodeKey = `${q("reached")}.${q("__node_key")}`;
|
|
396
|
+
const edgeTo = `${q("edges")}.${q("__to")}`;
|
|
397
|
+
const edgeToKey = `${q("edges")}.${q("__to_key")}`;
|
|
398
|
+
const edgeFromKey = `${q("edges")}.${q("__from_key")}`;
|
|
399
|
+
const eventId = `${q("edges")}.${q("__event_id")}`;
|
|
400
|
+
const eventStart = `${q("edges")}.${q("__event_start")}`;
|
|
401
|
+
const eventEnd = `${q("edges")}.${q("__event_end")}`;
|
|
402
|
+
const bestDistance = `${q("best")}.${q("distance")}`;
|
|
403
|
+
const gap = `${q("settings")}.${q("__gap")}`;
|
|
404
|
+
const firstAnchor = temporal.journeyAnchor("edges", direction);
|
|
405
|
+
const reachedAnchor = `${q("reached")}.${q("__journey_anchor")}`;
|
|
406
|
+
const firstElapsed = temporal.journeyElapsed("edges", firstAnchor, direction);
|
|
407
|
+
const candidateDistance = temporal.journeyElapsed("edges", reachedAnchor, direction);
|
|
408
|
+
const bestAnchor = `${direction === "outgoing" ? "MAX" : "MIN"}(${reachedAnchor})`;
|
|
409
|
+
return {
|
|
410
|
+
costRelation,
|
|
411
|
+
edgesRelation,
|
|
412
|
+
settingsRelation,
|
|
413
|
+
startsRelation,
|
|
414
|
+
withClause: `WITH RECURSIVE ${settingsRelation} AS MATERIALIZED (
|
|
415
|
+
SELECT CAST(? AS HUGEINT) AS ${q("__gap")}
|
|
416
|
+
), ${eventRowsRelation} AS MATERIALIZED (
|
|
417
|
+
${prepared.edges(direction, [
|
|
418
|
+
`${route.edgeWeight} AS ${q("__weight")}`,
|
|
419
|
+
`${route.typedEdgeId} AS ${q("__edge_id")}`,
|
|
420
|
+
`${route.edgeKey} AS ${q("__edge_key")}`,
|
|
421
|
+
...temporal.eventSelections("edges"),
|
|
422
|
+
])}
|
|
423
|
+
), ${edgesRelation} AS MATERIALIZED (
|
|
424
|
+
SELECT *
|
|
425
|
+
FROM ${eventRowsRelation} AS ${q("events")}
|
|
426
|
+
WHERE ${temporal.eventValidity("events")}
|
|
427
|
+
), ${startsRelation} AS MATERIALIZED (
|
|
428
|
+
${startsSelect}
|
|
429
|
+
), ${costRelation}(
|
|
430
|
+
${q("start")}, ${q("node")},
|
|
431
|
+
${q("__start_key")}, ${q("__node_key")},
|
|
432
|
+
${q("__event_id")}, ${q("__event_start")}, ${q("__event_end")},
|
|
433
|
+
${q("distance")}, ${q("__journey_anchor")}
|
|
434
|
+
) USING KEY(${q("__start_key")}, ${q("__event_id")}) AS (
|
|
435
|
+
SELECT ${start}, ${edgeTo}, ${startKey}, ${edgeToKey},
|
|
436
|
+
${eventId}, ${eventStart}, ${eventEnd}, ${firstElapsed}, ${firstAnchor}
|
|
437
|
+
FROM ${startsRelation} AS ${q("starts")}
|
|
438
|
+
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
439
|
+
ON ${startKey} = ${edgeFromKey}
|
|
440
|
+
UNION
|
|
441
|
+
SELECT ${reachedStart}, ${edgeTo}, ${reachedStartKey}, ${edgeToKey},
|
|
442
|
+
${eventId}, ${eventStart}, ${eventEnd},
|
|
443
|
+
MIN(${candidateDistance}) AS ${q("distance")}, ${bestAnchor}
|
|
444
|
+
FROM ${costRelation} AS ${q("reached")}
|
|
445
|
+
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
446
|
+
ON ${reachedNodeKey} = ${edgeFromKey}
|
|
447
|
+
CROSS JOIN ${settingsRelation} AS ${q("settings")}
|
|
448
|
+
LEFT JOIN recurring.${costRelation} AS ${q("best")}
|
|
449
|
+
ON ${reachedStartKey} = ${q("best")}.${q("__start_key")}
|
|
450
|
+
AND ${eventId} = ${q("best")}.${q("__event_id")}
|
|
451
|
+
WHERE ${temporal.transition("reached", "edges", direction, gap)}
|
|
452
|
+
GROUP BY ${reachedStart}, ${edgeTo}, ${reachedStartKey}, ${edgeToKey},
|
|
453
|
+
${eventId}, ${eventStart}, ${eventEnd}, ${bestDistance}
|
|
454
|
+
HAVING ${bestDistance} IS NULL OR
|
|
455
|
+
MIN(${candidateDistance}) < ${bestDistance}
|
|
456
|
+
)`,
|
|
457
|
+
};
|
|
337
458
|
}
|