@nshiab/simple-data-analysis-core 2.1.7 → 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 +187 -104
- package/esm/class/SimpleTable.d.ts.map +1 -1
- package/esm/class/SimpleTable.js +185 -102
- 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 +3 -3
- package/esm/helpers/graphRouteResultSelect.d.ts +1 -1
- package/esm/helpers/graphRouteResultSelect.d.ts.map +1 -1
- package/esm/helpers/graphRouteResultSelect.js +2 -2
- package/esm/helpers/prepareGraphMetricOptions.d.ts +2 -2
- package/esm/helpers/prepareGraphMetricOptions.d.ts.map +1 -1
- package/esm/helpers/prepareGraphMetricOptions.js +5 -4
- 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/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 +1 -1
- package/esm/methods/distances.d.ts.map +1 -1
- package/esm/methods/distances.js +235 -73
- package/esm/methods/shortestPath.d.ts +1 -1
- package/esm/methods/shortestPath.d.ts.map +1 -1
- package/esm/methods/shortestPath.js +44 -28
- package/llm.md +207 -112
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/script/class/SimpleTable.d.ts +187 -104
- package/script/class/SimpleTable.d.ts.map +1 -1
- package/script/class/SimpleTable.js +185 -102
- 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 +3 -3
- package/script/helpers/graphRouteResultSelect.d.ts +1 -1
- package/script/helpers/graphRouteResultSelect.d.ts.map +1 -1
- package/script/helpers/graphRouteResultSelect.js +2 -2
- package/script/helpers/prepareGraphMetricOptions.d.ts +2 -2
- package/script/helpers/prepareGraphMetricOptions.d.ts.map +1 -1
- package/script/helpers/prepareGraphMetricOptions.js +5 -4
- 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/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 +1 -1
- package/script/methods/distances.d.ts.map +1 -1
- package/script/methods/distances.js +235 -73
- package/script/methods/shortestPath.d.ts +1 -1
- package/script/methods/shortestPath.d.ts.map +1 -1
- package/script/methods/shortestPath.js +44 -28
package/esm/methods/distances.js
CHANGED
|
@@ -59,7 +59,10 @@ export default function distances(simpleTable, sourceColumn, targetColumn, start
|
|
|
59
59
|
return {
|
|
60
60
|
start: validated.idType,
|
|
61
61
|
node: validated.idType,
|
|
62
|
-
|
|
62
|
+
steps: "BIGINT",
|
|
63
|
+
...(options.weight === undefined
|
|
64
|
+
? {}
|
|
65
|
+
: { total: validated.distanceType }),
|
|
63
66
|
...(metrics.elapsedTime ? { elapsedTimeMs: "DOUBLE" } : {}),
|
|
64
67
|
};
|
|
65
68
|
},
|
|
@@ -84,12 +87,14 @@ function distancesSelect(input, schema, source, target, starts, direction, weigh
|
|
|
84
87
|
const edgeWeight = weightColumn === undefined
|
|
85
88
|
? `CAST(1 AS ${distanceType})`
|
|
86
89
|
: `CAST(${quoteIdentifier("edges")}.${quoteIdentifier(weightColumn.column)} AS ${distanceType})`;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const temporal = temporalOptions === undefined
|
|
91
|
+
? undefined
|
|
92
|
+
: prepareGraphTemporalSql(schema, temporalOptions, "distances()");
|
|
93
|
+
if (weight !== undefined || metrics.elapsedTime) {
|
|
94
|
+
return summaryDistancesSelect(prepared, direction, temporal, distanceType, edgeWeight, metrics, weight !== undefined, weightColumn?.type === "FLOAT" || weightColumn?.type === "DOUBLE");
|
|
95
|
+
}
|
|
96
|
+
if (temporal !== undefined) {
|
|
97
|
+
return temporalDistancesSelect(prepared, direction, temporal, distanceType, edgeWeight, "steps", false);
|
|
93
98
|
}
|
|
94
99
|
const relations = prepared.relationNames([
|
|
95
100
|
"graph_start_values",
|
|
@@ -113,6 +118,14 @@ function distancesSelect(input, schema, source, target, starts, direction, weigh
|
|
|
113
118
|
const edgeCost = `${quoteIdentifier("edges")}.${quoteIdentifier("__weight")}`;
|
|
114
119
|
const bestDistance = `${quoteIdentifier("best")}.${quoteIdentifier("distance")}`;
|
|
115
120
|
const candidateDistance = `CAST(${reachedDistance} + ${edgeCost} AS ${distanceType})`;
|
|
121
|
+
const candidateSteps = `${quoteIdentifier("reached")}.${quoteIdentifier("steps")} + 1`;
|
|
122
|
+
const bestSteps = `${quoteIdentifier("best")}.${quoteIdentifier("steps")}`;
|
|
123
|
+
const order = (distance, steps) => metrics.minimize === "steps"
|
|
124
|
+
? `ROW(${steps}, ${distance})`
|
|
125
|
+
: `ROW(${distance}, ${steps})`;
|
|
126
|
+
const candidateOrder = order(candidateDistance, candidateSteps);
|
|
127
|
+
const selectedDistance = `arg_min(${candidateDistance}, ${candidateOrder})`;
|
|
128
|
+
const selectedSteps = `arg_min(${candidateSteps}, ${candidateOrder})`;
|
|
116
129
|
return `WITH RECURSIVE ${startValuesRelation}(${quoteIdentifier("start")}) AS (
|
|
117
130
|
VALUES ${prepared.startValues}
|
|
118
131
|
), ${startsRelation} AS (
|
|
@@ -126,19 +139,20 @@ function distancesSelect(input, schema, source, target, starts, direction, weigh
|
|
|
126
139
|
), ${distancesRelation}(
|
|
127
140
|
${quoteIdentifier("start")}, ${quoteIdentifier("node")},
|
|
128
141
|
${quoteIdentifier("__start_key")}, ${quoteIdentifier("__node_key")},
|
|
129
|
-
${quoteIdentifier("distance")}
|
|
142
|
+
${quoteIdentifier("distance")}, ${quoteIdentifier("steps")}
|
|
130
143
|
) USING KEY(
|
|
131
144
|
${quoteIdentifier("__start_key")}, ${quoteIdentifier("__node_key")}
|
|
132
145
|
) AS (
|
|
133
146
|
SELECT ${start}, ${edgeTo}, ${startKey}, ${edgeToKey},
|
|
134
|
-
MIN(${edgeCost}) AS ${quoteIdentifier("distance")}
|
|
147
|
+
MIN(${edgeCost}) AS ${quoteIdentifier("distance")}, CAST(1 AS BIGINT)
|
|
135
148
|
FROM ${startsRelation} AS ${quoteIdentifier("starts")}
|
|
136
149
|
INNER JOIN ${edgesRelation} AS ${quoteIdentifier("edges")}
|
|
137
150
|
ON ${startKey} = ${edgeFromKey}
|
|
138
151
|
GROUP BY ${start}, ${edgeTo}, ${startKey}, ${edgeToKey}
|
|
139
152
|
UNION
|
|
140
153
|
SELECT ${reachedStart}, ${edgeTo}, ${reachedStartKey}, ${edgeToKey},
|
|
141
|
-
|
|
154
|
+
${selectedDistance} AS ${quoteIdentifier("distance")},
|
|
155
|
+
${selectedSteps} AS ${quoteIdentifier("steps")}
|
|
142
156
|
FROM ${distancesRelation} AS ${quoteIdentifier("reached")}
|
|
143
157
|
INNER JOIN ${edgesRelation} AS ${quoteIdentifier("edges")}
|
|
144
158
|
ON ${reachedNodeKey} = ${edgeFromKey}
|
|
@@ -146,111 +160,259 @@ function distancesSelect(input, schema, source, target, starts, direction, weigh
|
|
|
146
160
|
ON ${reachedStartKey} = ${quoteIdentifier("best")}.${quoteIdentifier("__start_key")}
|
|
147
161
|
AND ${edgeToKey} = ${quoteIdentifier("best")}.${quoteIdentifier("__node_key")}
|
|
148
162
|
GROUP BY ${reachedStart}, ${edgeTo}, ${reachedStartKey}, ${edgeToKey},
|
|
149
|
-
${bestDistance}
|
|
150
|
-
HAVING ${bestDistance} IS NULL OR
|
|
163
|
+
${bestDistance}, ${bestSteps}
|
|
164
|
+
HAVING ${bestDistance} IS NULL OR
|
|
165
|
+
${order(selectedDistance, selectedSteps)} < ${order(bestDistance, bestSteps)}
|
|
151
166
|
)
|
|
152
167
|
SELECT ${quoteIdentifier("start")}, ${quoteIdentifier("node")},
|
|
153
|
-
${quoteIdentifier("
|
|
168
|
+
${quoteIdentifier("steps")}${weight === undefined
|
|
169
|
+
? ""
|
|
170
|
+
: `, ${quoteIdentifier("distance")} AS ${quoteIdentifier("total")}`}
|
|
154
171
|
FROM ${distancesRelation}
|
|
155
|
-
ORDER BY ${quoteIdentifier("__start_key")}, ${quoteIdentifier("distance")}, ${quoteIdentifier("__node_key")}`;
|
|
172
|
+
ORDER BY ${quoteIdentifier("__start_key")}, ${quoteIdentifier(metrics.minimize === "steps" ? "steps" : "distance")}, ${quoteIdentifier("__node_key")}`;
|
|
156
173
|
}
|
|
157
|
-
function temporalDistancesSelect(prepared, direction, temporal, distanceType, edgeWeight) {
|
|
174
|
+
function temporalDistancesSelect(prepared, direction, temporal, distanceType, edgeWeight, minimize, includeTotal) {
|
|
158
175
|
const q = quoteIdentifier;
|
|
159
176
|
const startsSelect = `SELECT ${q("start")},
|
|
160
177
|
${prepared.key(q("start"))} AS ${q("__key")}
|
|
161
178
|
FROM (VALUES ${prepared.startValues}) AS ${q("start_values")}(${q("start")})`;
|
|
162
|
-
const costStates = buildGraphTemporalCostStateSql(prepared, startsSelect, direction, temporal, distanceType, edgeWeight);
|
|
179
|
+
const costStates = buildGraphTemporalCostStateSql(prepared, startsSelect, direction, temporal, distanceType, edgeWeight, [], minimize);
|
|
180
|
+
const order = minimize === "steps"
|
|
181
|
+
? `${q("steps")}, ${q("distance")}`
|
|
182
|
+
: `${q("distance")}, ${q("steps")}`;
|
|
163
183
|
return `${costStates.withClause}
|
|
164
|
-
SELECT ${q("start")}, ${q("node")},
|
|
165
|
-
MIN(${q("distance")}) AS ${q("total")}
|
|
184
|
+
SELECT ${q("start")}, ${q("node")}, ${q("steps")}${includeTotal ? `, ${q("distance")} AS ${q("total")}` : ""}
|
|
166
185
|
FROM ${costStates.costRelation}
|
|
167
|
-
|
|
168
|
-
${q("__node_key")}
|
|
169
|
-
|
|
186
|
+
QUALIFY row_number() OVER (
|
|
187
|
+
PARTITION BY ${q("__start_key")}, ${q("__node_key")}
|
|
188
|
+
ORDER BY ${order}
|
|
189
|
+
) = 1
|
|
190
|
+
ORDER BY ${q("__start_key")}, ${q(minimize === "steps" ? "steps" : "distance")}, ${q("__node_key")}`;
|
|
170
191
|
}
|
|
171
|
-
|
|
192
|
+
// Distinct visited sets, metrics, and temporal boundaries are enough to extend
|
|
193
|
+
// summaries. No edge IDs or full connection sequences are needed. Keeping the
|
|
194
|
+
// visited set is necessary: equal summaries can have different legal extensions.
|
|
195
|
+
function summaryDistancesSelect(prepared, direction, temporal, distanceType, edgeWeight, metrics, includeTotal, floatingWeight) {
|
|
172
196
|
const q = quoteIdentifier;
|
|
173
197
|
const relations = prepared.relationNames([
|
|
174
198
|
"graph_starts",
|
|
175
199
|
"graph_temporal_settings",
|
|
176
200
|
"graph_event_rows",
|
|
177
201
|
"graph_edges",
|
|
178
|
-
"
|
|
179
|
-
"
|
|
202
|
+
"graph_summaries",
|
|
203
|
+
"graph_metric_results",
|
|
204
|
+
"graph_tied_summaries",
|
|
205
|
+
"graph_best_metrics",
|
|
206
|
+
"graph_metric_bounds",
|
|
180
207
|
]);
|
|
181
208
|
const starts = relations.graph_starts;
|
|
182
209
|
const settings = relations.graph_temporal_settings;
|
|
183
210
|
const events = relations.graph_event_rows;
|
|
184
211
|
const edges = relations.graph_edges;
|
|
185
|
-
const
|
|
186
|
-
const results = relations.
|
|
212
|
+
const summaries = relations.graph_summaries;
|
|
213
|
+
const results = relations.graph_metric_results;
|
|
214
|
+
const tied = relations.graph_tied_summaries;
|
|
187
215
|
const ref = (alias, column) => `${q(alias)}.${q(column)}`;
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
216
|
+
const searchDirection = direction;
|
|
217
|
+
const firstAnchor = temporal?.journeyAnchor("edges", searchDirection);
|
|
218
|
+
const sum = `CAST(${ref("reached", "distance")} + ${ref("edges", "__weight")} AS ${distanceType})`;
|
|
219
|
+
// Reported costs must not reject routes before the primary objective has
|
|
220
|
+
// selected its results. NULL marks overflow and propagates through sums;
|
|
221
|
+
// non-negative weights cannot bring an overflowing total back into range.
|
|
222
|
+
const nextDistance = metrics.minimize === "weight" ? sum : `TRY(${sum})`;
|
|
223
|
+
const temporalColumns = temporal === undefined
|
|
224
|
+
? ""
|
|
225
|
+
: `, ${q("__event_start")}, ${q("__event_end")}`;
|
|
226
|
+
const eventValues = temporal === undefined
|
|
227
|
+
? ""
|
|
228
|
+
: `, ${ref("edges", "__event_start")}, ${ref("edges", "__event_end")}`;
|
|
229
|
+
const metric = metrics.minimize === "steps"
|
|
230
|
+
? q("steps")
|
|
231
|
+
: metrics.minimize === "weight"
|
|
232
|
+
? q("distance")
|
|
233
|
+
: q("__elapsed");
|
|
234
|
+
// Use native time precision for selection. Collapse equal reported values
|
|
235
|
+
// after conversion to milliseconds, since these are summary rows.
|
|
236
|
+
const reportedElapsed = metrics.elapsedTime
|
|
237
|
+
? `, ${temporal.elapsedMilliseconds(q("__elapsed"))} AS ${q("elapsedTimeMs")}`
|
|
238
|
+
: "";
|
|
239
|
+
const order = metrics.minimize === "steps"
|
|
240
|
+
? [
|
|
241
|
+
q("steps"),
|
|
242
|
+
q("__node_key"),
|
|
243
|
+
...(includeTotal ? [q("total")] : []),
|
|
244
|
+
...(metrics.elapsedTime ? [q("elapsedTimeMs")] : []),
|
|
245
|
+
]
|
|
246
|
+
: metrics.minimize === "weight"
|
|
247
|
+
? [
|
|
248
|
+
q("total"),
|
|
249
|
+
q("__node_key"),
|
|
250
|
+
q("steps"),
|
|
251
|
+
...(metrics.elapsedTime ? [q("elapsedTimeMs")] : []),
|
|
252
|
+
]
|
|
253
|
+
: [
|
|
254
|
+
q("elapsedTimeMs"),
|
|
255
|
+
q("__node_key"),
|
|
256
|
+
q("steps"),
|
|
257
|
+
...(includeTotal ? [q("total")] : []),
|
|
258
|
+
];
|
|
259
|
+
const bounds = summaryBoundsSql(relations.graph_best_metrics, relations.graph_metric_bounds, starts, edges, settings, temporal, searchDirection, metrics.minimize, distanceType, floatingWeight);
|
|
260
|
+
return `WITH RECURSIVE ${settings} AS (
|
|
261
|
+
${temporal === undefined
|
|
262
|
+
? `SELECT 0 AS ${q("__gap")}`
|
|
263
|
+
: `SELECT CAST(? AS HUGEINT) AS ${q("__gap")}`}
|
|
200
264
|
), ${events} AS MATERIALIZED (
|
|
201
265
|
${prepared.edges(direction, [
|
|
202
266
|
`${edgeWeight} AS ${q("__weight")}`,
|
|
203
|
-
...temporal
|
|
267
|
+
...(temporal?.eventSelections("edges") ?? []),
|
|
204
268
|
])}
|
|
205
269
|
), ${edges} AS MATERIALIZED (
|
|
206
|
-
SELECT
|
|
207
|
-
|
|
208
|
-
|
|
270
|
+
SELECT DISTINCT ${q("__from")}, ${q("__to")}, ${q("__from_key")},
|
|
271
|
+
${q("__to_key")}, ${q("__weight")}${temporalColumns}
|
|
272
|
+
FROM ${events} AS ${q("events")}
|
|
273
|
+
${temporal === undefined ? "" : `WHERE ${temporal.eventValidity("events")}`}
|
|
274
|
+
), ${starts} AS (
|
|
209
275
|
SELECT ${q("start")}, ${prepared.key(q("start"))} AS ${q("__key")}
|
|
210
276
|
FROM (VALUES ${prepared.startValues}) AS ${q("start_values")}(${q("start")})
|
|
211
|
-
), ${
|
|
277
|
+
), ${bounds.ctes}, ${summaries}(
|
|
212
278
|
${q("start")}, ${q("node")}, ${q("__start_key")}, ${q("__node_key")},
|
|
213
|
-
${q("
|
|
214
|
-
|
|
215
|
-
) USING KEY(${q("__start_key")}, ${q("__event_id")}, ${q("__anchor")}) AS (
|
|
279
|
+
${q("__visited")}, ${q("steps")}, ${q("distance")}${temporalColumns}${metrics.elapsedTime ? `, ${q("__anchor")}` : ""}
|
|
280
|
+
) AS (
|
|
216
281
|
SELECT ${ref("starts", "start")}, ${ref("edges", "__to")},
|
|
217
282
|
${ref("starts", "__key")}, ${ref("edges", "__to_key")},
|
|
218
|
-
${ref("
|
|
219
|
-
${ref("edges", "
|
|
283
|
+
list_sort(list_distinct([${ref("starts", "__key")}, ${ref("edges", "__to_key")}])),
|
|
284
|
+
CAST(1 AS BIGINT), ${ref("edges", "__weight")}${eventValues}${metrics.elapsedTime ? `, ${firstAnchor}` : ""}
|
|
220
285
|
FROM ${starts} AS ${q("starts")}
|
|
221
286
|
INNER JOIN ${edges} AS ${q("edges")}
|
|
222
287
|
ON ${ref("starts", "__key")} = ${ref("edges", "__from_key")}
|
|
288
|
+
${bounds.join(ref("starts", "__key"))}
|
|
289
|
+
WHERE ${bounds.test("CAST(1 AS BIGINT)", ref("edges", "__weight"), firstAnchor)}
|
|
223
290
|
UNION
|
|
224
291
|
SELECT ${ref("reached", "start")}, ${ref("edges", "__to")},
|
|
225
292
|
${ref("reached", "__start_key")}, ${ref("edges", "__to_key")},
|
|
226
|
-
${ref("
|
|
227
|
-
${ref("
|
|
228
|
-
|
|
229
|
-
FROM ${states} AS ${q("reached")}
|
|
293
|
+
list_sort(list_distinct(list_append(${ref("reached", "__visited")}, ${ref("edges", "__to_key")}))),
|
|
294
|
+
${ref("reached", "steps")} + 1, ${nextDistance}${eventValues}${metrics.elapsedTime ? `, ${ref("reached", "__anchor")}` : ""}
|
|
295
|
+
FROM ${summaries} AS ${q("reached")}
|
|
230
296
|
INNER JOIN ${edges} AS ${q("edges")}
|
|
231
297
|
ON ${ref("reached", "__node_key")} = ${ref("edges", "__from_key")}
|
|
232
298
|
CROSS JOIN ${settings} AS ${q("settings")}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
AND ${ref("
|
|
236
|
-
AND ${
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
${ref("reached", "__start_key")}, ${ref("edges", "__to_key")},
|
|
240
|
-
${ref("edges", "__event_id")}, ${ref("edges", "__event_start")},
|
|
241
|
-
${ref("edges", "__event_end")}, ${reachedAnchor}, ${ref("best", "distance")}
|
|
242
|
-
HAVING ${ref("best", "distance")} IS NULL OR
|
|
243
|
-
MIN(${candidateDistance}) < ${ref("best", "distance")}
|
|
299
|
+
${bounds.join(ref("reached", "__start_key"))}
|
|
300
|
+
WHERE ${bounds.test(`${ref("reached", "steps")} + 1`, nextDistance, ref("reached", "__anchor"))}
|
|
301
|
+
AND ${ref("reached", "__node_key")} <> ${ref("reached", "__start_key")}
|
|
302
|
+
AND (NOT list_contains(${ref("reached", "__visited")}, ${ref("edges", "__to_key")})
|
|
303
|
+
OR ${ref("edges", "__to_key")} = ${ref("reached", "__start_key")})
|
|
304
|
+
${temporal === undefined ? "" : `AND ${temporal.transition("reached", "edges", searchDirection, ref("settings", "__gap"))}`}
|
|
244
305
|
), ${results} AS (
|
|
245
|
-
SELECT
|
|
246
|
-
|
|
306
|
+
SELECT *${metrics.elapsedTime
|
|
307
|
+
? `, ${temporal.journeyElapsed("summaries", ref("summaries", "__anchor"), searchDirection)} AS ${q("__elapsed")}`
|
|
308
|
+
: ""}
|
|
309
|
+
FROM ${summaries} AS ${q("summaries")}
|
|
310
|
+
), ${tied} AS MATERIALIZED (
|
|
311
|
+
SELECT DISTINCT ${q("start")}, ${q("node")}, ${q("__start_key")}, ${q("__node_key")},
|
|
312
|
+
${q("steps")}${includeTotal ? `, ${q("distance")} AS ${q("total")}` : ""}${reportedElapsed}
|
|
313
|
+
FROM ${results}
|
|
314
|
+
QUALIFY ${metric} = MIN(${metric}) OVER (
|
|
315
|
+
PARTITION BY ${q("__start_key")}, ${q("__node_key")}
|
|
316
|
+
)
|
|
247
317
|
)
|
|
248
|
-
SELECT ${q("start")}, ${q("node")}, ${q("
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
)
|
|
255
|
-
|
|
318
|
+
SELECT ${q("start")}, ${q("node")}, ${q("steps")}${includeTotal
|
|
319
|
+
? `, CASE WHEN ${q("total")} IS NULL
|
|
320
|
+
THEN error('distances() total exceeds the supported ${distanceType} range for an optimal route.')
|
|
321
|
+
ELSE ${q("total")} END AS ${q("total")}`
|
|
322
|
+
: ""}${metrics.elapsedTime ? `, ${q("elapsedTimeMs")}` : ""}
|
|
323
|
+
FROM ${tied}
|
|
324
|
+
ORDER BY ${q("__start_key")}, ${order.join(", ")}`;
|
|
325
|
+
}
|
|
326
|
+
// Compute primary optima without enumerating visited sets. Exact additive
|
|
327
|
+
// objectives have optimal prefixes (at the same event boundary in temporal
|
|
328
|
+
// traversal). Floating addition can erase prefix differences, and elapsed
|
|
329
|
+
// optima can depend on a later departure, so those use a per-start upper bound.
|
|
330
|
+
function summaryBoundsSql(best, limits, starts, edges, settings, temporal, direction, minimize, distanceType, floatingWeight) {
|
|
331
|
+
const q = quoteIdentifier;
|
|
332
|
+
const ref = (alias, column) => `${q(alias)}.${q(column)}`;
|
|
333
|
+
const elapsed = minimize === "elapsedTime";
|
|
334
|
+
const globalBound = elapsed || (minimize === "weight" && floatingWeight);
|
|
335
|
+
const boundaries = temporal === undefined
|
|
336
|
+
? []
|
|
337
|
+
: ["__event_start", "__event_end"];
|
|
338
|
+
const keys = [
|
|
339
|
+
"__start_key",
|
|
340
|
+
"__node_key",
|
|
341
|
+
...boundaries,
|
|
342
|
+
...(elapsed ? ["__anchor"] : []),
|
|
343
|
+
];
|
|
344
|
+
const firstAnchor = elapsed
|
|
345
|
+
? temporal.journeyAnchor("edges", direction)
|
|
346
|
+
: undefined;
|
|
347
|
+
const firstKeys = [
|
|
348
|
+
ref("starts", "__key"),
|
|
349
|
+
ref("edges", "__to_key"),
|
|
350
|
+
...boundaries.map((key) => ref("edges", key)),
|
|
351
|
+
...(elapsed ? [firstAnchor] : []),
|
|
352
|
+
];
|
|
353
|
+
const nextKeys = [
|
|
354
|
+
ref("reached", "__start_key"),
|
|
355
|
+
ref("edges", "__to_key"),
|
|
356
|
+
...boundaries.map((key) => ref("edges", key)),
|
|
357
|
+
...(elapsed ? [ref("reached", "__anchor")] : []),
|
|
358
|
+
];
|
|
359
|
+
const firstMetric = minimize === "steps"
|
|
360
|
+
? "CAST(1 AS BIGINT)"
|
|
361
|
+
: elapsed
|
|
362
|
+
? temporal.journeyElapsed("edges", firstAnchor, direction)
|
|
363
|
+
: ref("edges", "__weight");
|
|
364
|
+
const nextMetric = minimize === "steps"
|
|
365
|
+
? `${ref("reached", "__metric")} + 1`
|
|
366
|
+
: elapsed
|
|
367
|
+
? temporal.journeyElapsed("edges", ref("reached", "__anchor"), direction)
|
|
368
|
+
: `CAST(${ref("reached", "__metric")} + ${ref("edges", "__weight")} AS ${distanceType})`;
|
|
369
|
+
const minimum = `MIN(${nextMetric})`;
|
|
370
|
+
const bestMetric = ref("best", "__metric");
|
|
371
|
+
const ctes = `${best}(${keys.map(q).join(", ")}, ${q("__metric")})
|
|
372
|
+
USING KEY(${keys.map(q).join(", ")}) AS (
|
|
373
|
+
SELECT ${firstKeys.join(", ")}, MIN(${firstMetric})
|
|
374
|
+
FROM ${starts} AS ${q("starts")}
|
|
375
|
+
INNER JOIN ${edges} AS ${q("edges")}
|
|
376
|
+
ON ${ref("starts", "__key")} = ${ref("edges", "__from_key")}
|
|
377
|
+
GROUP BY ${firstKeys.join(", ")}
|
|
378
|
+
UNION
|
|
379
|
+
SELECT ${nextKeys.join(", ")}, ${minimum}
|
|
380
|
+
FROM ${best} AS ${q("reached")}
|
|
381
|
+
INNER JOIN ${edges} AS ${q("edges")}
|
|
382
|
+
ON ${ref("reached", "__node_key")} = ${ref("edges", "__from_key")}
|
|
383
|
+
CROSS JOIN ${settings} AS ${q("settings")}
|
|
384
|
+
LEFT JOIN recurring.${best} AS ${q("best")}
|
|
385
|
+
ON ${keys.map((key, i) => `${ref("best", key)} = ${nextKeys[i]}`).join(" AND ")}
|
|
386
|
+
WHERE ${ref("reached", "__node_key")} <> ${ref("reached", "__start_key")}
|
|
387
|
+
${temporal === undefined ? "" : `AND ${temporal.transition("reached", "edges", direction, ref("settings", "__gap"))}`}
|
|
388
|
+
GROUP BY ${nextKeys.join(", ")}, ${bestMetric}
|
|
389
|
+
HAVING ${bestMetric} IS NULL OR ${minimum} < ${bestMetric}
|
|
390
|
+
)${globalBound
|
|
391
|
+
? `, ${limits} AS (
|
|
392
|
+
SELECT ${q("__start_key")}, MAX(${q("__minimum")}) AS ${q("__metric")}
|
|
393
|
+
FROM (
|
|
394
|
+
SELECT ${q("__start_key")}, ${q("__node_key")}, MIN(${q("__metric")}) AS ${q("__minimum")}
|
|
395
|
+
FROM ${best}
|
|
396
|
+
GROUP BY ${q("__start_key")}, ${q("__node_key")}
|
|
397
|
+
) AS ${q("minima")}
|
|
398
|
+
GROUP BY ${q("__start_key")}
|
|
399
|
+
)`
|
|
400
|
+
: ""}`;
|
|
401
|
+
return {
|
|
402
|
+
ctes,
|
|
403
|
+
join: (startKey) => `INNER JOIN ${globalBound ? limits : best} AS ${q("bounds")}
|
|
404
|
+
ON ${ref("bounds", "__start_key")} = ${startKey}
|
|
405
|
+
${globalBound
|
|
406
|
+
? ""
|
|
407
|
+
: `AND ${ref("bounds", "__node_key")} = ${ref("edges", "__to_key")}
|
|
408
|
+
${boundaries.map((key) => `AND ${ref("bounds", key)} = ${ref("edges", key)}`).join(" ")}`}`,
|
|
409
|
+
test: (steps, distance, anchor) => {
|
|
410
|
+
const metric = minimize === "steps"
|
|
411
|
+
? steps
|
|
412
|
+
: elapsed
|
|
413
|
+
? temporal.journeyElapsed("edges", anchor, direction)
|
|
414
|
+
: distance;
|
|
415
|
+
return `${metric} ${globalBound ? "<=" : "="} ${ref("bounds", "__metric")}`;
|
|
416
|
+
},
|
|
417
|
+
};
|
|
256
418
|
}
|
|
@@ -7,7 +7,7 @@ type ShortestPathOptions = GraphTemporalOptions & {
|
|
|
7
7
|
outputTable?: string | boolean;
|
|
8
8
|
weight?: string;
|
|
9
9
|
elapsedTime?: boolean;
|
|
10
|
-
minimize?: "weight" | "elapsedTime";
|
|
10
|
+
minimize?: "steps" | "weight" | "elapsedTime";
|
|
11
11
|
};
|
|
12
12
|
export default function shortestPath(simpleTable: SimpleTable, sourceColumn: string, targetColumn: string, edgeId: string, start: GraphId, end: GraphId, options?: ShortestPathOptions): SimpleTable;
|
|
13
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;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,QAAQ,GAAG,aAAa,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"}
|
|
@@ -53,7 +53,7 @@ export default function shortestPath(simpleTable, sourceColumn, targetColumn, ed
|
|
|
53
53
|
? undefined
|
|
54
54
|
: (input) => validateGraphTemporalEvents(input, temporalOptions, "shortestPath()", parameters),
|
|
55
55
|
values: (schema) => {
|
|
56
|
-
validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "shortestPath()");
|
|
56
|
+
validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "shortestPath()", options.weight !== undefined);
|
|
57
57
|
const temporal = temporalOptions === undefined
|
|
58
58
|
? undefined
|
|
59
59
|
: prepareGraphTemporalSql(schema, temporalOptions, "shortestPath()");
|
|
@@ -63,27 +63,28 @@ export default function shortestPath(simpleTable, sourceColumn, targetColumn, ed
|
|
|
63
63
|
},
|
|
64
64
|
buildSelect: (input, schema) => shortestPathSelect(input, schema, sourceColumn, targetColumn, edgeId, endpoints, direction, options.weight, temporalOptions, metrics),
|
|
65
65
|
outputSchema: (schema) => {
|
|
66
|
-
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);
|
|
67
67
|
if (temporalOptions !== undefined) {
|
|
68
68
|
prepareGraphTemporalSql(schema, temporalOptions, "shortestPath()");
|
|
69
69
|
}
|
|
70
|
-
return graphRouteResultSchema(schema, validated.distanceType, "shortestPath()", metrics.elapsedTime);
|
|
70
|
+
return graphRouteResultSchema(schema, validated.distanceType, "shortestPath()", metrics.elapsedTime, options.weight !== undefined);
|
|
71
71
|
},
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
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()");
|
|
75
|
+
const route = prepareGraphRouteSql(input, schema, source, target, edgeId, routeEndpoints, weight, "shortestPath()", weight !== undefined);
|
|
76
76
|
const prepared = route.traversal;
|
|
77
77
|
if (temporalOptions !== undefined) {
|
|
78
78
|
const temporal = prepareGraphTemporalSql(schema, temporalOptions, "shortestPath()");
|
|
79
|
-
return temporalShortestPathSelect(route, direction, temporal, metrics);
|
|
79
|
+
return temporalShortestPathSelect(route, direction, temporal, metrics, weight !== undefined);
|
|
80
80
|
}
|
|
81
81
|
const distanceType = route.distanceType;
|
|
82
82
|
// Exact shortest routes have optimal prefixes. Floating addition can erase
|
|
83
83
|
// a prefix-cost difference later, so retain all simple prefixes up to the
|
|
84
84
|
// best complete cost before selecting the full-route ties.
|
|
85
|
-
const floating =
|
|
86
|
-
route.weightType === "DOUBLE";
|
|
85
|
+
const floating = metrics.minimize === "weight" &&
|
|
86
|
+
(route.weightType === "FLOAT" || route.weightType === "DOUBLE");
|
|
87
|
+
const metricType = metrics.minimize === "steps" ? "BIGINT" : distanceType;
|
|
87
88
|
const relations = prepared.relationNames([
|
|
88
89
|
"graph_route_endpoints",
|
|
89
90
|
"graph_edges",
|
|
@@ -109,8 +110,17 @@ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoint
|
|
|
109
110
|
const edgeToKey = `${q("edges")}.${q("__to_key")}`;
|
|
110
111
|
const edgeTo = `${q("edges")}.${q("__to")}`;
|
|
111
112
|
const edgeCost = `${q("edges")}.${q("__weight")}`;
|
|
113
|
+
const edgeMetric = metrics.minimize === "steps"
|
|
114
|
+
? "CAST(1 AS BIGINT)"
|
|
115
|
+
: edgeCost;
|
|
112
116
|
const routeDistance = `${q("routes")}.${q("distance")}`;
|
|
113
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;
|
|
114
124
|
return `WITH RECURSIVE ${endpointRelation}(
|
|
115
125
|
${q("start")}, ${q("end")}, ${q("__start_key")}, ${q("__end_key")}
|
|
116
126
|
) AS (
|
|
@@ -142,13 +152,13 @@ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoint
|
|
|
142
152
|
${q("node")}, ${q("__node_key")}, ${q("distance")}
|
|
143
153
|
) USING KEY(${q("__node_key")}) AS (
|
|
144
154
|
SELECT ${q("start")}, ${q("__start_key")},
|
|
145
|
-
CAST(0 AS ${
|
|
155
|
+
CAST(0 AS ${metricType})
|
|
146
156
|
FROM ${endpointRelation}
|
|
147
157
|
INNER JOIN ${toEndRelation}
|
|
148
158
|
ON ${q("__start_key")} = ${q("__key")}
|
|
149
159
|
UNION
|
|
150
160
|
SELECT ${edgeTo}, ${edgeToKey},
|
|
151
|
-
MIN(CAST(${q("reached")}.${q("distance")} + ${
|
|
161
|
+
MIN(CAST(${q("reached")}.${q("distance")} + ${edgeMetric} AS ${metricType}))
|
|
152
162
|
FROM ${distancesRelation} AS ${q("reached")}
|
|
153
163
|
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
154
164
|
ON ${q("reached")}.${q("__node_key")} = ${edgeFromKey}
|
|
@@ -158,7 +168,7 @@ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoint
|
|
|
158
168
|
ON ${edgeToKey} = ${q("best")}.${q("__node_key")}
|
|
159
169
|
GROUP BY ${edgeTo}, ${edgeToKey}, ${q("best")}.${q("distance")}
|
|
160
170
|
HAVING ${q("best")}.${q("distance")} IS NULL
|
|
161
|
-
OR MIN(CAST(${q("reached")}.${q("distance")} + ${
|
|
171
|
+
OR MIN(CAST(${q("reached")}.${q("distance")} + ${edgeMetric} AS ${metricType})) < ${q("best")}.${q("distance")}
|
|
162
172
|
), ${bestTotalRelation} AS (
|
|
163
173
|
SELECT ${q("distances")}.${q("distance")}
|
|
164
174
|
FROM ${distancesRelation} AS ${q("distances")}
|
|
@@ -197,23 +207,23 @@ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoint
|
|
|
197
207
|
WHERE ${q("routes")}.${q("__node_key")} <> ${q("__end_key")}
|
|
198
208
|
AND NOT list_contains(${q("routes")}.${q("__visited")}, ${edgeToKey})
|
|
199
209
|
AND ${floating
|
|
200
|
-
? `${
|
|
201
|
-
: `${
|
|
210
|
+
? `${candidateMetric} <= ${q("best_total")}.${q("distance")}`
|
|
211
|
+
: `${candidateMetric} = ${q("best_prefix")}.${q("distance")}`}
|
|
202
212
|
), ${shortestRelation} AS (
|
|
203
213
|
SELECT ${q("__edge_keys")}, ${q("steps")}
|
|
204
|
-
FROM ${routesRelation}
|
|
214
|
+
FROM ${routesRelation} AS ${q("routes")}
|
|
205
215
|
CROSS JOIN ${endpointRelation}
|
|
206
216
|
CROSS JOIN ${bestTotalRelation} AS ${q("best_total")}
|
|
207
217
|
WHERE ${q("__node_key")} = ${q("__end_key")}
|
|
208
|
-
AND ${
|
|
218
|
+
AND ${routeMetric} = ${q("best_total")}.${q("distance")}
|
|
209
219
|
), ${rankedRelation} AS (
|
|
210
220
|
SELECT CAST(row_number() OVER (ORDER BY ${q("__edge_keys")}) - 1 AS BIGINT) AS ${q("pathId")},
|
|
211
221
|
${q("steps")}
|
|
212
222
|
FROM ${shortestRelation}
|
|
213
223
|
)
|
|
214
|
-
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn)}`;
|
|
224
|
+
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn, false, false, weight !== undefined)}`;
|
|
215
225
|
}
|
|
216
|
-
function temporalShortestPathSelect(route, direction, temporal, metrics) {
|
|
226
|
+
function temporalShortestPathSelect(route, direction, temporal, metrics, includeTotal) {
|
|
217
227
|
const prepared = route.traversal;
|
|
218
228
|
const q = quoteIdentifier;
|
|
219
229
|
const startsSelect = `SELECT ${q("start")},
|
|
@@ -226,7 +236,7 @@ function temporalShortestPathSelect(route, direction, temporal, metrics) {
|
|
|
226
236
|
: buildGraphTemporalCostStateSql(prepared, startsSelect, direction, temporal, route.distanceType, route.edgeWeight, [
|
|
227
237
|
`${route.typedEdgeId} AS ${q("__edge_id")}`,
|
|
228
238
|
`${route.edgeKey} AS ${q("__edge_key")}`,
|
|
229
|
-
]);
|
|
239
|
+
], metrics.minimize === "steps" ? "steps" : "weight");
|
|
230
240
|
const relations = prepared.relationNames([
|
|
231
241
|
"graph_route_endpoints",
|
|
232
242
|
"graph_nodes",
|
|
@@ -256,15 +266,21 @@ function temporalShortestPathSelect(route, direction, temporal, metrics) {
|
|
|
256
266
|
const routeAnchor = `${q("routes")}.${q("__journey_anchor")}`;
|
|
257
267
|
const firstElapsed = temporal.journeyElapsed("edges", firstAnchor, direction);
|
|
258
268
|
const candidateElapsed = temporal.journeyElapsed("edges", routeAnchor, direction);
|
|
259
|
-
const firstMetric = metrics.minimize === "
|
|
260
|
-
?
|
|
261
|
-
:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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;
|
|
268
284
|
const step = (distance, elapsed) => `struct_pack(
|
|
269
285
|
${q("edgeId")} := ${q("edges")}.${q("__edge_id")},
|
|
270
286
|
${q("source")} := ${q("edges")}.${q("__from")},
|
|
@@ -297,7 +313,7 @@ function temporalShortestPathSelect(route, direction, temporal, metrics) {
|
|
|
297
313
|
INNER JOIN ${edgesRelation} AS ${q("edges")}
|
|
298
314
|
ON ${q("reached")}.${q("__key")} = ${edgeToKey}
|
|
299
315
|
), ${bestTotalRelation} AS (
|
|
300
|
-
SELECT MIN(${q("costs")}.${q("distance")}) AS ${q("distance")}
|
|
316
|
+
SELECT MIN(${q("costs")}.${q(metrics.minimize === "steps" ? "steps" : "distance")}) AS ${q("distance")}
|
|
301
317
|
FROM ${costs.costRelation} AS ${q("costs")}
|
|
302
318
|
INNER JOIN ${endpointRelation}
|
|
303
319
|
ON ${q("costs")}.${q("__node_key")} = ${q("__end_key")}
|
|
@@ -352,7 +368,7 @@ function temporalShortestPathSelect(route, direction, temporal, metrics) {
|
|
|
352
368
|
AS ${q("pathId")}, ${q("steps")}
|
|
353
369
|
FROM ${shortestRelation}
|
|
354
370
|
)
|
|
355
|
-
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn, false, metrics.elapsedTime)}`;
|
|
371
|
+
${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn, false, metrics.elapsedTime, includeTotal)}`;
|
|
356
372
|
}
|
|
357
373
|
// At the same physical event a later departure (or earlier incoming arrival)
|
|
358
374
|
// dominates every future elapsed duration. Keyed improvements find the optimum
|