@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.
Files changed (75) hide show
  1. package/esm/class/SimpleTable.d.ts +399 -90
  2. package/esm/class/SimpleTable.d.ts.map +1 -1
  3. package/esm/class/SimpleTable.js +393 -90
  4. package/esm/helpers/buildGraphTemporalCostStateSql.d.ts +1 -1
  5. package/esm/helpers/buildGraphTemporalCostStateSql.d.ts.map +1 -1
  6. package/esm/helpers/buildGraphTemporalCostStateSql.js +23 -9
  7. package/esm/helpers/graphRouteResultSchema.d.ts +1 -1
  8. package/esm/helpers/graphRouteResultSchema.d.ts.map +1 -1
  9. package/esm/helpers/graphRouteResultSchema.js +4 -3
  10. package/esm/helpers/graphRouteResultSelect.d.ts +1 -1
  11. package/esm/helpers/graphRouteResultSelect.d.ts.map +1 -1
  12. package/esm/helpers/graphRouteResultSelect.js +5 -2
  13. package/esm/helpers/prepareGraphMetricOptions.d.ts +14 -0
  14. package/esm/helpers/prepareGraphMetricOptions.d.ts.map +1 -0
  15. package/esm/helpers/prepareGraphMetricOptions.js +41 -0
  16. package/esm/helpers/prepareGraphRouteSql.d.ts +2 -2
  17. package/esm/helpers/prepareGraphRouteSql.d.ts.map +1 -1
  18. package/esm/helpers/prepareGraphRouteSql.js +4 -4
  19. package/esm/helpers/prepareGraphTemporalSql.d.ts +3 -0
  20. package/esm/helpers/prepareGraphTemporalSql.d.ts.map +1 -1
  21. package/esm/helpers/prepareGraphTemporalSql.js +7 -0
  22. package/esm/helpers/validateGraphRouteResultSchema.d.ts +1 -1
  23. package/esm/helpers/validateGraphRouteResultSchema.d.ts.map +1 -1
  24. package/esm/helpers/validateGraphRouteResultSchema.js +3 -1
  25. package/esm/methods/distances.d.ts +2 -0
  26. package/esm/methods/distances.d.ts.map +1 -1
  27. package/esm/methods/distances.js +274 -20
  28. package/esm/methods/findCycles.d.ts +1 -0
  29. package/esm/methods/findCycles.d.ts.map +1 -1
  30. package/esm/methods/findCycles.js +22 -15
  31. package/esm/methods/paths.d.ts +1 -0
  32. package/esm/methods/paths.d.ts.map +1 -1
  33. package/esm/methods/paths.js +21 -14
  34. package/esm/methods/shortestPath.d.ts +2 -0
  35. package/esm/methods/shortestPath.d.ts.map +1 -1
  36. package/esm/methods/shortestPath.js +155 -34
  37. package/llm.md +428 -108
  38. package/llms.txt +1 -1
  39. package/package.json +1 -1
  40. package/script/class/SimpleTable.d.ts +399 -90
  41. package/script/class/SimpleTable.d.ts.map +1 -1
  42. package/script/class/SimpleTable.js +393 -90
  43. package/script/helpers/buildGraphTemporalCostStateSql.d.ts +1 -1
  44. package/script/helpers/buildGraphTemporalCostStateSql.d.ts.map +1 -1
  45. package/script/helpers/buildGraphTemporalCostStateSql.js +23 -9
  46. package/script/helpers/graphRouteResultSchema.d.ts +1 -1
  47. package/script/helpers/graphRouteResultSchema.d.ts.map +1 -1
  48. package/script/helpers/graphRouteResultSchema.js +4 -3
  49. package/script/helpers/graphRouteResultSelect.d.ts +1 -1
  50. package/script/helpers/graphRouteResultSelect.d.ts.map +1 -1
  51. package/script/helpers/graphRouteResultSelect.js +5 -2
  52. package/script/helpers/prepareGraphMetricOptions.d.ts +14 -0
  53. package/script/helpers/prepareGraphMetricOptions.d.ts.map +1 -0
  54. package/script/helpers/prepareGraphMetricOptions.js +44 -0
  55. package/script/helpers/prepareGraphRouteSql.d.ts +2 -2
  56. package/script/helpers/prepareGraphRouteSql.d.ts.map +1 -1
  57. package/script/helpers/prepareGraphRouteSql.js +4 -4
  58. package/script/helpers/prepareGraphTemporalSql.d.ts +3 -0
  59. package/script/helpers/prepareGraphTemporalSql.d.ts.map +1 -1
  60. package/script/helpers/prepareGraphTemporalSql.js +7 -0
  61. package/script/helpers/validateGraphRouteResultSchema.d.ts +1 -1
  62. package/script/helpers/validateGraphRouteResultSchema.d.ts.map +1 -1
  63. package/script/helpers/validateGraphRouteResultSchema.js +3 -1
  64. package/script/methods/distances.d.ts +2 -0
  65. package/script/methods/distances.d.ts.map +1 -1
  66. package/script/methods/distances.js +274 -20
  67. package/script/methods/findCycles.d.ts +1 -0
  68. package/script/methods/findCycles.d.ts.map +1 -1
  69. package/script/methods/findCycles.js +22 -15
  70. package/script/methods/paths.d.ts +1 -0
  71. package/script/methods/paths.d.ts.map +1 -1
  72. package/script/methods/paths.js +21 -14
  73. package/script/methods/shortestPath.d.ts +2 -0
  74. package/script/methods/shortestPath.d.ts.map +1 -1
  75. package/script/methods/shortestPath.js +155 -34
@@ -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 step = (distance) => `struct_pack(
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;AAGvD,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;CACjB,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,CAmHb"}
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 = route.weightType === "FLOAT" ||
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 ${distanceType})
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")} + ${edgeCost} AS ${distanceType}))
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")} + ${edgeCost} AS ${distanceType})) < ${q("best")}.${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
- ? `${candidateDistance} <= ${q("best_total")}.${q("distance")}`
199
- : `${candidateDistance} = ${q("best_prefix")}.${q("distance")}`}
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 ${routesRelation}.${q("distance")} = ${q("best_total")}.${q("distance")}
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 = buildGraphTemporalCostStateSql(prepared, startsSelect, direction, temporal, route.distanceType, route.edgeWeight, [
223
- `${route.typedEdgeId} AS ${q("__edge_id")}`,
224
- `${route.edgeKey} AS ${q("__edge_key")}`,
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 step = (distance) => `struct_pack(
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 ${edgeCost} <= ${q("best_total")}.${q("distance")}
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 ${candidateDistance} <= ${q("best_total")}.${q("distance")}
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 ${routeDistance} = ${q("best_total")}.${q("distance")}
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
  }