@honua/react 0.1.1-beta.0 → 0.1.4-beta.0
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/contract/discovery.d.ts +34 -0
- package/contract/discovery.d.ts.map +1 -1
- package/contract/index.d.ts +7 -5
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +3 -2
- package/contract/pmtiles.d.ts +10 -1
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +10 -2
- package/contract/protocol-module.d.ts +84 -9
- package/contract/protocol-module.d.ts.map +1 -1
- package/contract/query-filter-fes.d.ts +18 -0
- package/contract/query-filter-fes.d.ts.map +1 -0
- package/contract/query-filter-fes.js +122 -0
- package/contract/query-filter.d.ts +300 -0
- package/contract/query-filter.d.ts.map +1 -0
- package/contract/query-filter.js +883 -0
- package/contract/schema.d.ts +12 -0
- package/contract/schema.d.ts.map +1 -1
- package/contract/schema.js +34 -0
- package/contract/source-capability-support.d.ts.map +1 -1
- package/contract/source-capability-support.js +15 -3
- package/contract/source.d.ts +45 -25
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +719 -479
- package/contract/types.d.ts +87 -18
- package/contract/types.d.ts.map +1 -1
- package/contract/widget-source.d.ts.map +1 -1
- package/core/client.d.ts +45 -21
- package/core/client.d.ts.map +1 -1
- package/core/client.js +224 -46
- package/core/error-classifications.d.ts +6 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +6 -0
- package/core/error-code-registry.d.ts +6 -0
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +6 -0
- package/core/error-envelope.js +2 -0
- package/core/errors.d.ts +1 -1
- package/core/errors.js +1 -1
- package/core/geoparquet-sql.d.ts +68 -4
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +325 -8
- package/core/odata-write-codec.d.ts.map +1 -1
- package/core/odata-write-codec.js +0 -1
- package/core/odata.d.ts +6 -0
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +6 -1
- package/core/ogc-processes.d.ts +182 -25
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +602 -30
- package/core/process-runner.d.ts +8 -1
- package/core/process-runner.d.ts.map +1 -1
- package/core/process-runner.js +1 -0
- package/core/protocol-transport.d.ts +2 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/stac.d.ts +31 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +136 -19
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +273 -68
- package/core/types.d.ts +123 -9
- package/core/types.d.ts.map +1 -1
- package/core/wfs-axis-order.d.ts +18 -0
- package/core/wfs-axis-order.d.ts.map +1 -0
- package/core/wfs-axis-order.js +108 -0
- package/core/wfs-capabilities.d.ts +65 -24
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +237 -27
- package/core/wfs-filter.d.ts +26 -3
- package/core/wfs-filter.d.ts.map +1 -1
- package/core/wfs-filter.js +48 -53
- package/core/wfs-protocol-error.d.ts +12 -0
- package/core/wfs-protocol-error.d.ts.map +1 -0
- package/core/wfs-protocol-error.js +13 -0
- package/core/wfs.d.ts +53 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +251 -29
- package/core/wms-feature-info.d.ts +92 -0
- package/core/wms-feature-info.d.ts.map +1 -0
- package/core/wms-feature-info.js +285 -0
- package/core/wms.d.ts +9 -0
- package/core/wms.d.ts.map +1 -1
- package/core/wms.js +17 -5
- package/esri-compat/event-bus.d.ts +20 -0
- package/esri-compat/event-bus.d.ts.map +1 -1
- package/esri-compat/feature-filter.d.ts +46 -1
- package/esri-compat/feature-filter.d.ts.map +1 -1
- package/esri-compat/feature-filter.js +49 -0
- package/esri-compat/geojson-layer.d.ts +4 -2
- package/esri-compat/geojson-layer.d.ts.map +1 -1
- package/esri-compat/geojson-layer.js +4 -2
- package/esri-compat/identity-manager.js +2 -2
- package/esri-compat/imagery-layer.d.ts +2 -1
- package/esri-compat/imagery-layer.d.ts.map +1 -1
- package/esri-compat/imagery-layer.js +2 -1
- package/esri-compat/layer-list.d.ts +8 -1
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +11 -4
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +1 -1
- package/esri-compat/locator.d.ts +297 -0
- package/esri-compat/locator.d.ts.map +1 -0
- package/esri-compat/locator.js +579 -0
- package/esri-compat/map-image-layer.d.ts.map +1 -1
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +1 -1
- package/esri-compat/scene-view.d.ts.map +1 -1
- package/esri-compat/time-slider.d.ts +29 -0
- package/esri-compat/time-slider.d.ts.map +1 -1
- package/esri-compat/time-slider.js +117 -0
- package/esri-compat/wfs-layer.d.ts +2 -1
- package/esri-compat/wfs-layer.d.ts.map +1 -1
- package/esri-compat/wfs-layer.js +2 -1
- package/esri-compat/widget-host.d.ts +42 -6
- package/esri-compat/widget-host.d.ts.map +1 -1
- package/esri-compat/widget-host.js +99 -10
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/runtime.d.ts.map +1 -1
- package/map/data-to-map-bridge.d.ts +1 -1
- package/map/data-to-map-bridge.d.ts.map +1 -1
- package/map/data-to-map-bridge.js +1 -2
- package/map/honua-map.d.ts.map +1 -1
- package/map/honua-map.js +1 -1
- package/map/index.d.ts +2 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +2 -0
- package/map/realtime-reconciliation-adapter.d.ts +98 -0
- package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
- package/map/realtime-reconciliation-adapter.js +84 -0
- package/map/temporal-playback.d.ts +25 -0
- package/map/temporal-playback.d.ts.map +1 -1
- package/map/temporal-playback.js +29 -1
- package/package.json +4 -3
- package/query-planner/canonical-filter.d.ts +30 -0
- package/query-planner/canonical-filter.d.ts.map +1 -0
- package/query-planner/canonical-filter.js +30 -0
- package/query-planner/columnar-plan.d.ts +63 -0
- package/query-planner/columnar-plan.d.ts.map +1 -0
- package/query-planner/columnar-plan.js +94 -0
- package/query-planner/diagnostics.d.ts +3 -3
- package/query-planner/diagnostics.d.ts.map +1 -1
- package/query-planner/diagnostics.js +17 -4
- package/query-planner/duckdb.d.ts.map +1 -1
- package/query-planner/duckdb.js +16 -2
- package/query-planner/execution-context.d.ts.map +1 -1
- package/query-planner/execution-context.js +3 -1
- package/query-planner/executor.d.ts.map +1 -1
- package/query-planner/executor.js +7 -1
- package/query-planner/geoservices.d.ts.map +1 -1
- package/query-planner/geoservices.js +24 -5
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +29 -5
- package/query-planner/index.d.ts +20 -5
- package/query-planner/index.d.ts.map +1 -1
- package/query-planner/index.js +16 -3
- package/query-planner/ir.d.ts +6 -1
- package/query-planner/ir.d.ts.map +1 -1
- package/query-planner/ir.js +63 -1
- package/query-planner/odata-protocol-module.d.ts +54 -0
- package/query-planner/odata-protocol-module.d.ts.map +1 -0
- package/query-planner/odata-protocol-module.js +199 -0
- package/query-planner/odata-v1.d.ts +23 -0
- package/query-planner/odata-v1.d.ts.map +1 -0
- package/query-planner/odata-v1.js +202 -0
- package/query-planner/odata.d.ts +0 -3
- package/query-planner/odata.d.ts.map +1 -1
- package/query-planner/odata.js +0 -143
- package/query-planner/ogc-features.d.ts.map +1 -1
- package/query-planner/ogc-features.js +23 -1
- package/query-planner/planner.d.ts +2 -2
- package/query-planner/planner.d.ts.map +1 -1
- package/query-planner/planner.js +146 -27
- package/query-planner/representation.d.ts +59 -0
- package/query-planner/representation.d.ts.map +1 -0
- package/query-planner/representation.js +140 -0
- package/query-planner/types.d.ts +153 -3
- package/query-planner/types.d.ts.map +1 -1
- package/query-planner/wfs-protocol-module.d.ts +57 -0
- package/query-planner/wfs-protocol-module.d.ts.map +1 -0
- package/query-planner/wfs-protocol-module.js +731 -0
- package/query-planner/wfs-v1.d.ts +41 -0
- package/query-planner/wfs-v1.d.ts.map +1 -0
- package/query-planner/wfs-v1.js +366 -0
- package/query-planner/wfs.d.ts +1 -3
- package/query-planner/wfs.d.ts.map +1 -1
- package/query-planner/wfs.js +1 -70
- package/react/honua-map.d.ts +3 -1
- package/react/honua-map.d.ts.map +1 -1
- package/react/honua-map.js +11 -1
- package/react/hooks.d.ts +1 -1
- package/react/hooks.js +1 -1
- package/react/selection.d.ts.map +1 -1
- package/react/selection.js +0 -0
- package/react/use-mounted-source.d.ts +1 -1
- package/react/use-mounted-source.js +1 -1
- package/realtime/checkpoint-store.d.ts +206 -0
- package/realtime/checkpoint-store.d.ts.map +1 -0
- package/realtime/checkpoint-store.js +504 -0
- package/realtime/contract.d.ts +142 -0
- package/realtime/contract.d.ts.map +1 -0
- package/realtime/contract.js +154 -0
- package/realtime/exploration.d.ts +16 -0
- package/realtime/exploration.d.ts.map +1 -0
- package/realtime/exploration.js +32 -0
- package/realtime/honua-server.d.ts +131 -0
- package/realtime/honua-server.d.ts.map +1 -0
- package/realtime/honua-server.js +302 -0
- package/realtime/index.d.ts +39 -0
- package/realtime/index.d.ts.map +1 -0
- package/realtime/index.js +26 -0
- package/realtime/odata-delta.d.ts +114 -0
- package/realtime/odata-delta.d.ts.map +1 -0
- package/realtime/odata-delta.js +483 -0
- package/realtime/projections.d.ts +30 -0
- package/realtime/projections.d.ts.map +1 -0
- package/realtime/projections.js +51 -0
- package/realtime/reconciliation.d.ts +245 -0
- package/realtime/reconciliation.d.ts.map +1 -0
- package/realtime/reconciliation.js +377 -0
- package/realtime/reducer.d.ts +14 -0
- package/realtime/reducer.d.ts.map +1 -0
- package/realtime/reducer.js +351 -0
- package/realtime/resumable-transport.d.ts +111 -0
- package/realtime/resumable-transport.d.ts.map +1 -0
- package/realtime/resumable-transport.js +406 -0
- package/realtime/resumable.d.ts +104 -0
- package/realtime/resumable.d.ts.map +1 -0
- package/realtime/resumable.js +632 -0
- package/realtime/sse.d.ts +23 -0
- package/realtime/sse.d.ts.map +1 -0
- package/realtime/sse.js +147 -0
- package/realtime/store.d.ts +18 -0
- package/realtime/store.d.ts.map +1 -0
- package/realtime/store.js +63 -0
- package/realtime/types.d.ts +190 -0
- package/realtime/types.d.ts.map +1 -0
- package/realtime/types.js +10 -0
- package/realtime/websocket.d.ts +60 -0
- package/realtime/websocket.d.ts.map +1 -0
- package/realtime/websocket.js +157 -0
- package/realtime/wire.d.ts +28 -0
- package/realtime/wire.d.ts.map +1 -0
- package/realtime/wire.js +73 -0
- package/runtime/index.d.ts +6 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -1
- package/runtime/load-package.d.ts +24 -0
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +45 -1
- package/runtime/maplibre-renderer.d.ts +11 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -1
- package/runtime/maplibre-renderer.js +32 -7
- package/runtime/offline-region-protocol.d.ts +73 -0
- package/runtime/offline-region-protocol.d.ts.map +1 -0
- package/runtime/offline-region-protocol.js +63 -0
- package/runtime/offline-region-style.d.ts +78 -0
- package/runtime/offline-region-style.d.ts.map +1 -0
- package/runtime/offline-region-style.js +147 -0
- package/runtime/pmtiles-protocol.d.ts +7 -8
- package/runtime/pmtiles-protocol.d.ts.map +1 -1
- package/runtime/pmtiles-protocol.js +19 -66
- package/runtime/protocol-registry.d.ts +55 -0
- package/runtime/protocol-registry.d.ts.map +1 -0
- package/runtime/protocol-registry.js +103 -0
- package/runtime/runtime.d.ts +2 -0
- package/runtime/runtime.d.ts.map +1 -1
- package/runtime/runtime.js +6 -1
- package/studio/index.d.ts +24 -7
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +21 -7
- package/studio/lifecycle-client.d.ts +164 -0
- package/studio/lifecycle-client.d.ts.map +1 -0
- package/studio/lifecycle-client.js +299 -0
- package/studio/lifecycle-errors.d.ts +91 -0
- package/studio/lifecycle-errors.d.ts.map +1 -0
- package/studio/lifecycle-errors.js +77 -0
- package/studio/lifecycle-types.d.ts +321 -0
- package/studio/lifecycle-types.d.ts.map +1 -0
- package/studio/lifecycle-types.js +22 -0
- package/webmap/convert-label.js +0 -1
- package/webmap/convert-symbol.d.ts.map +1 -1
package/query-planner/planner.js
CHANGED
|
@@ -5,11 +5,12 @@ import { compileDuckDbQuery, compileDuckDbQueryV2 } from "./duckdb.js";
|
|
|
5
5
|
import { compileGeoServicesQuery } from "./geoservices.js";
|
|
6
6
|
import { compileGrpcQuery } from "./grpc.js";
|
|
7
7
|
import { createGeoParquetQueryIr, createQueryIr, deepFreeze, queryFromCanonical } from "./ir.js";
|
|
8
|
-
import { compileOdataQuery } from "./odata.js";
|
|
8
|
+
import { compileOdataQuery, odataProtocolQueryCompiler } from "./odata-v1.js";
|
|
9
9
|
import { compileOgcApiFeaturesQuery } from "./ogc-features.js";
|
|
10
|
+
import { createQueryPlanRepresentationDecision, normalizeRepresentationRequest } from "./representation.js";
|
|
10
11
|
import { parseGeoParquetResourceHandle } from "./resource.js";
|
|
11
12
|
import { HonuaQueryPlanExecutionError, HonuaQueryPlanningError, MAX_LOCAL_MATERIALIZATION_ROWS, QUERY_PLAN_DIAGNOSTICS_VERSION, QUERY_PLAN_KIND, QUERY_PLAN_V2_VERSION, QUERY_PLAN_VERSION, } from "./types.js";
|
|
12
|
-
import { compileWfsQuery } from "./wfs.js";
|
|
13
|
+
import { compileWfsQuery, wfsProtocolQueryCompiler } from "./wfs-v1.js";
|
|
13
14
|
export function explainQuery(options) {
|
|
14
15
|
const resource = ownGeoParquetResource(options);
|
|
15
16
|
if (!resource && options.descriptor.protocol === "geoparquet")
|
|
@@ -19,6 +20,9 @@ export function explainQuery(options) {
|
|
|
19
20
|
const fallback = normalizeFallback(options.fallback);
|
|
20
21
|
const estimates = normalizeEstimates(options.estimates);
|
|
21
22
|
const executionMode = normalizeExecutionMode(options.executionMode);
|
|
23
|
+
// Representation is chosen before any step is compiled so an unsatisfiable
|
|
24
|
+
// explicit pin fails closed before the plan takes shape.
|
|
25
|
+
const representation = createQueryPlanRepresentationDecision(ir, estimates, normalizeRepresentationRequest(options.representation));
|
|
22
26
|
let baseSteps;
|
|
23
27
|
let pushdown;
|
|
24
28
|
if (ir.query.aggregation && !ir.source.capabilities.includes("queryAggregate")) {
|
|
@@ -30,7 +34,8 @@ export function explainQuery(options) {
|
|
|
30
34
|
}
|
|
31
35
|
rejectUnsafeEstimate(fallback, estimates.rows, estimates.bytes);
|
|
32
36
|
const ogcFallback = ir.source.protocol === "ogc-features";
|
|
33
|
-
const
|
|
37
|
+
const adapterOwnsLookahead = ogcFallback || ir.source.protocol === "odata" || ir.source.protocol === "wfs";
|
|
38
|
+
const inputQuery = localAggregateInputQuery(ir.query, ir.query.aggregation, fallback.maxRows, options.descriptor.schema?.primaryKey, { preserveGeometry: ogcFallback, adapterOwnsLookahead });
|
|
34
39
|
baseSteps = [
|
|
35
40
|
{
|
|
36
41
|
id: "remote-input",
|
|
@@ -86,8 +91,8 @@ export function explainQuery(options) {
|
|
|
86
91
|
const { fidelity, losses } = summarizeFidelity(steps);
|
|
87
92
|
const bounds = summarizePlanBounds(steps);
|
|
88
93
|
const cache = createCacheDecision(options.cache);
|
|
89
|
-
const validity = createPlanValidity(ir, provenance, capabilityPolicy, fallback, executionMode);
|
|
90
|
-
const warnings = createPlanWarnings(baseSteps, ir.source);
|
|
94
|
+
const validity = createPlanValidity(ir, provenance, capabilityPolicy, fallback, executionMode, representation.selected);
|
|
95
|
+
const warnings = createPlanWarnings(baseSteps, ir.source, representation);
|
|
91
96
|
const unsigned = {
|
|
92
97
|
kind: QUERY_PLAN_KIND,
|
|
93
98
|
version: ir.version === "2.0" ? QUERY_PLAN_V2_VERSION : QUERY_PLAN_VERSION,
|
|
@@ -102,6 +107,7 @@ export function explainQuery(options) {
|
|
|
102
107
|
cache,
|
|
103
108
|
provenance,
|
|
104
109
|
validity,
|
|
110
|
+
representation,
|
|
105
111
|
estimates,
|
|
106
112
|
steps,
|
|
107
113
|
warnings,
|
|
@@ -120,9 +126,9 @@ function compileRemoteQuery(source, query, operation = "query") {
|
|
|
120
126
|
case "ogc-features":
|
|
121
127
|
return ogcQueryAllWireRequest(compileOgcApiFeaturesQuery(source, query), operation);
|
|
122
128
|
case "wfs":
|
|
123
|
-
return
|
|
129
|
+
return wfsProtocolQueryCompiler.compile({ source, query, operation });
|
|
124
130
|
case "odata":
|
|
125
|
-
return
|
|
131
|
+
return odataProtocolQueryCompiler.compile({ source, query, operation });
|
|
126
132
|
case "geoparquet":
|
|
127
133
|
return isGeoParquetV2Source(source)
|
|
128
134
|
? compileDuckDbQueryV2(source, query)
|
|
@@ -191,14 +197,14 @@ export function validateQueryPlanSnapshot(plan) {
|
|
|
191
197
|
throw invalidPersistedPlan();
|
|
192
198
|
return snapshot.plan;
|
|
193
199
|
}
|
|
194
|
-
/** Canonical persistence boundary
|
|
200
|
+
/** Canonical persistence boundary; validated legacy protocol artifacts are upgraded before serialization. */
|
|
195
201
|
export function serializeQueryPlan(plan) {
|
|
196
202
|
const snapshot = persistedPlanSnapshot(plan);
|
|
197
203
|
if (snapshot.hash !== snapshot.plan.fingerprint)
|
|
198
204
|
throw invalidPersistedPlan();
|
|
199
205
|
return canonicalStringify(toJsonValue(snapshot.plan));
|
|
200
206
|
}
|
|
201
|
-
/** Parse a persisted plan
|
|
207
|
+
/** Parse a persisted plan, upgrading validated legacy protocol artifacts without retaining unsafe input. */
|
|
202
208
|
export function parseQueryPlan(serialized) {
|
|
203
209
|
if (typeof serialized !== "string")
|
|
204
210
|
throw invalidPersistedPlan();
|
|
@@ -255,6 +261,7 @@ export function migrateGeoParquetQueryPlanV1(legacyPlan, resourceValue) {
|
|
|
255
261
|
...(parsed.ir.source.sourceVersion !== undefined ? { sourceVersion: parsed.ir.source.sourceVersion } : {}),
|
|
256
262
|
authorizationScope: parsed.ir.source.authorizationScope,
|
|
257
263
|
estimates: parsed.estimates,
|
|
264
|
+
representation: normalizeRepresentationRequest(persistedRepresentationRequest(parsed)),
|
|
258
265
|
});
|
|
259
266
|
}
|
|
260
267
|
function ownGeoParquetResource(options) {
|
|
@@ -294,15 +301,11 @@ function assertPlanPersistenceSafe(plan) {
|
|
|
294
301
|
throw invalidPersistedPlan();
|
|
295
302
|
}
|
|
296
303
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
throw invalidPersistedPlan();
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
const expected = rebuildQueryPlanV1(plan);
|
|
304
|
+
const expected = isLegacyOdataQueryPlanV1(plan)
|
|
305
|
+
? rebuildLegacyOdataQueryPlanV1(plan)
|
|
306
|
+
: isLegacyWfsQueryPlanV1(plan)
|
|
307
|
+
? rebuildLegacyWfsQueryPlanV1(plan)
|
|
308
|
+
: rebuildQueryPlanV1(plan);
|
|
306
309
|
if (canonicalStringify(toJsonValue(plan)) !== canonicalStringify(toJsonValue(expected))) {
|
|
307
310
|
throw invalidPersistedPlan();
|
|
308
311
|
}
|
|
@@ -324,6 +327,10 @@ function assertStructuredDiagnostics(plan) {
|
|
|
324
327
|
const fallback = normalizeFallback(plan.fallback);
|
|
325
328
|
const estimates = normalizeEstimates(plan.estimates);
|
|
326
329
|
const executionMode = normalizeExecutionMode(plan.validity.executionMode);
|
|
330
|
+
// Re-derive the representation decision from the persisted request plus the
|
|
331
|
+
// persisted IR/estimates, so a tampered `selected`, `available`, or `reason`
|
|
332
|
+
// cannot survive the canonical comparison below.
|
|
333
|
+
const representation = createQueryPlanRepresentationDecision(plan.ir, estimates, normalizeRepresentationRequest(persistedRepresentationRequest(plan)));
|
|
327
334
|
if (!sameCanonicalValue(plan.fallback, fallback) || !sameCanonicalValue(plan.estimates, estimates)) {
|
|
328
335
|
throw invalidPersistedPlan();
|
|
329
336
|
}
|
|
@@ -363,8 +370,9 @@ function assertStructuredDiagnostics(plan) {
|
|
|
363
370
|
bounds: summarizePlanBounds(expectedSteps),
|
|
364
371
|
cache: createCacheDecision(persistedCacheOptions(plan.cache)),
|
|
365
372
|
provenance: expectedProvenance,
|
|
366
|
-
validity: createPlanValidity(plan.ir, expectedProvenance, plan.capabilityPolicy, fallback, executionMode),
|
|
367
|
-
|
|
373
|
+
validity: createPlanValidity(plan.ir, expectedProvenance, plan.capabilityPolicy, fallback, executionMode, representation.selected),
|
|
374
|
+
representation,
|
|
375
|
+
warnings: createPlanWarnings(plan.steps, plan.ir.source, representation),
|
|
368
376
|
};
|
|
369
377
|
const actual = {
|
|
370
378
|
diagnosticsVersion: plan.diagnosticsVersion,
|
|
@@ -375,11 +383,19 @@ function assertStructuredDiagnostics(plan) {
|
|
|
375
383
|
cache: plan.cache,
|
|
376
384
|
provenance: plan.provenance,
|
|
377
385
|
validity: plan.validity,
|
|
386
|
+
representation: plan.representation,
|
|
378
387
|
warnings: plan.warnings,
|
|
379
388
|
};
|
|
380
389
|
if (!sameCanonicalValue(actual, expected))
|
|
381
390
|
throw invalidPersistedPlan();
|
|
382
391
|
}
|
|
392
|
+
/** Read the persisted representation request without trusting any other member of the decision. */
|
|
393
|
+
function persistedRepresentationRequest(plan) {
|
|
394
|
+
const decision = plan.representation;
|
|
395
|
+
if (decision === null || typeof decision !== "object" || Array.isArray(decision))
|
|
396
|
+
throw invalidPersistedPlan();
|
|
397
|
+
return decision.requested;
|
|
398
|
+
}
|
|
383
399
|
function persistedStepPushdown(step) {
|
|
384
400
|
if (step.engine === "client") {
|
|
385
401
|
if (step.operation !== "aggregate")
|
|
@@ -415,8 +431,9 @@ function rebuildPersistedProvenance(plan) {
|
|
|
415
431
|
});
|
|
416
432
|
}
|
|
417
433
|
/** Rebuild legacy plans through the same pure planner to bind every persisted step to the IR. */
|
|
418
|
-
function rebuildQueryPlanV1(plan) {
|
|
434
|
+
function rebuildQueryPlanV1(plan, options = {}) {
|
|
419
435
|
const source = plan.ir.source;
|
|
436
|
+
const sourceAuthorityFingerprint = parseSourceAuthorityFingerprint(source.authorityFingerprint);
|
|
420
437
|
const capabilitiesValue = parsePlanCapabilities(source.capabilities);
|
|
421
438
|
const geometryProperty = parseOptionalPlanMetadata(source.geometryProperty);
|
|
422
439
|
const primaryKey = persistedV1FallbackPrimaryKey(plan);
|
|
@@ -479,10 +496,110 @@ function rebuildQueryPlanV1(plan) {
|
|
|
479
496
|
...(source.schemaVersion !== undefined ? { schemaVersion: source.schemaVersion } : {}),
|
|
480
497
|
...(source.sourceVersion !== undefined ? { sourceVersion: source.sourceVersion } : {}),
|
|
481
498
|
authorizationScope: source.authorizationScope,
|
|
499
|
+
...(sourceAuthorityFingerprint
|
|
500
|
+
? { sourceAuthorityFingerprint }
|
|
501
|
+
: options.legacyWfsAuthority
|
|
502
|
+
? { sourceAuthorityFingerprint: null }
|
|
503
|
+
: {}),
|
|
482
504
|
estimates: plan.estimates,
|
|
483
505
|
cache: persistedCacheOptions(plan.cache),
|
|
484
506
|
discovery: persistedDiscoveryContext(plan.provenance.discovery),
|
|
485
507
|
executionMode: plan.validity.executionMode,
|
|
508
|
+
representation: normalizeRepresentationRequest(persistedRepresentationRequest(plan)),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
function parseSourceAuthorityFingerprint(value) {
|
|
512
|
+
if (value === undefined)
|
|
513
|
+
return undefined;
|
|
514
|
+
if (typeof value !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value)) {
|
|
515
|
+
throw invalidPersistedPlan();
|
|
516
|
+
}
|
|
517
|
+
return value;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Rebuild the exact OData plan representation emitted before operation-bound
|
|
521
|
+
* protocol artifacts were introduced. The persisted plan version remains
|
|
522
|
+
* 1.0, so those integrity-checked snapshots must be validated against their
|
|
523
|
+
* original compiler semantics before they can be migrated.
|
|
524
|
+
*/
|
|
525
|
+
function rebuildLegacyOdataQueryPlanV1(plan) {
|
|
526
|
+
return rebuildLegacyProtocolQueryPlanV1(plan, "odata", compileOdataQuery);
|
|
527
|
+
}
|
|
528
|
+
function isLegacyOdataQueryPlanV1(plan) {
|
|
529
|
+
if (plan.version !== QUERY_PLAN_VERSION || plan.ir.version !== "1.0" || plan.ir.source.protocol !== "odata") {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
const remoteSteps = plan.steps.filter((step) => step.engine === "remote");
|
|
533
|
+
return remoteSteps.length > 0 && remoteSteps.every((step) => step.compiled.compiler === "odata-v4-query-v1");
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Rebuild the exact WFS plan representation emitted before operation-bound
|
|
537
|
+
* protocol artifacts were introduced. This validates the old artifact against
|
|
538
|
+
* the old compiler before the same-version plan is migrated.
|
|
539
|
+
*/
|
|
540
|
+
function rebuildLegacyWfsQueryPlanV1(plan) {
|
|
541
|
+
return rebuildLegacyProtocolQueryPlanV1(plan, "wfs", compileWfsQuery);
|
|
542
|
+
}
|
|
543
|
+
function isLegacyWfsQueryPlanV1(plan) {
|
|
544
|
+
if (plan.version !== QUERY_PLAN_VERSION || plan.ir.version !== "1.0" || plan.ir.source.protocol !== "wfs") {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
const remoteSteps = plan.steps.filter((step) => step.engine === "remote");
|
|
548
|
+
return remoteSteps.length > 0 && remoteSteps.every((step) => step.compiled.compiler === "wfs-2.0-get-feature-v1");
|
|
549
|
+
}
|
|
550
|
+
function rebuildLegacyProtocolQueryPlanV1(plan, protocol, compile) {
|
|
551
|
+
const modern = rebuildQueryPlanV1(plan, {
|
|
552
|
+
legacyWfsAuthority: protocol === "wfs" && plan.ir.source.authorityFingerprint === undefined,
|
|
553
|
+
});
|
|
554
|
+
if (modern.ir.source.protocol !== protocol)
|
|
555
|
+
throw invalidPersistedPlan();
|
|
556
|
+
const baseSteps = modern.steps.map((step) => {
|
|
557
|
+
const { fidelity: _fidelity, losses: _losses, bounds: _bounds, provenance: _provenance, ...base } = step;
|
|
558
|
+
if (base.engine !== "remote")
|
|
559
|
+
return base;
|
|
560
|
+
const query = base.operation === "queryAll" ? legacyProtocolQueryAllInput(base.query, modern.fallback) : base.query;
|
|
561
|
+
return {
|
|
562
|
+
...base,
|
|
563
|
+
query,
|
|
564
|
+
compiled: compile(modern.ir.source, query),
|
|
565
|
+
};
|
|
566
|
+
});
|
|
567
|
+
const steps = baseSteps.map((step) => {
|
|
568
|
+
const diagnostics = createStepDiagnostics(step, modern.estimates, modern.provenance);
|
|
569
|
+
return step.engine === "client" ? { ...step, ...diagnostics, fidelity: "equivalent" } : { ...step, ...diagnostics };
|
|
570
|
+
});
|
|
571
|
+
const { fidelity, losses } = summarizeFidelity(steps);
|
|
572
|
+
const bounds = summarizePlanBounds(steps);
|
|
573
|
+
const warnings = createPlanWarnings(baseSteps, modern.ir.source, modern.representation);
|
|
574
|
+
const { id: _id, fingerprint: _fingerprint, fidelity: _modernFidelity, losses: _modernLosses, bounds: _modernBounds, steps: _modernSteps, warnings: _modernWarnings, ...shared } = modern;
|
|
575
|
+
const unsigned = {
|
|
576
|
+
...shared,
|
|
577
|
+
fidelity,
|
|
578
|
+
losses,
|
|
579
|
+
bounds,
|
|
580
|
+
steps,
|
|
581
|
+
warnings,
|
|
582
|
+
};
|
|
583
|
+
const fingerprint = sha256(canonicalStringify(toJsonValue(unsigned)));
|
|
584
|
+
return deepFreeze({
|
|
585
|
+
...unsigned,
|
|
586
|
+
id: `plan_${fingerprint.slice("sha256:".length, "sha256:".length + 16)}`,
|
|
587
|
+
fingerprint,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
function legacyProtocolQueryAllInput(query, fallback) {
|
|
591
|
+
if (fallback.mode !== "bounded-local")
|
|
592
|
+
throw invalidPersistedPlan();
|
|
593
|
+
const pagination = query.pagination;
|
|
594
|
+
if (!pagination ||
|
|
595
|
+
pagination.offset !== 0 ||
|
|
596
|
+
pagination.limit !== fallback.maxRows ||
|
|
597
|
+
fallback.maxRows >= Number.MAX_SAFE_INTEGER) {
|
|
598
|
+
throw invalidPersistedPlan();
|
|
599
|
+
}
|
|
600
|
+
return deepFreeze({
|
|
601
|
+
...query,
|
|
602
|
+
pagination: { offset: 0, limit: fallback.maxRows + 1 },
|
|
486
603
|
});
|
|
487
604
|
}
|
|
488
605
|
function persistedV1FallbackPrimaryKey(plan) {
|
|
@@ -576,6 +693,7 @@ function rebuildGeoParquetQueryPlanV2(plan) {
|
|
|
576
693
|
cache: persistedCacheOptions(plan.cache),
|
|
577
694
|
discovery: persistedDiscoveryContext(plan.provenance.discovery),
|
|
578
695
|
executionMode: plan.validity.executionMode,
|
|
696
|
+
representation: normalizeRepresentationRequest(persistedRepresentationRequest(plan)),
|
|
579
697
|
});
|
|
580
698
|
}
|
|
581
699
|
function parsePersistedGeoParquetGeometryEncoding(value) {
|
|
@@ -670,8 +788,9 @@ function persistedPlanSnapshot(value) {
|
|
|
670
788
|
assertPlanPersistenceSafe(plan);
|
|
671
789
|
if (containsCredentialBearingPlanMaterial(plan))
|
|
672
790
|
throw invalidPersistedPlan();
|
|
673
|
-
const
|
|
674
|
-
|
|
791
|
+
const acceptedPlan = isLegacyOdataQueryPlanV1(plan) || isLegacyWfsQueryPlanV1(plan) ? rebuildQueryPlanV1(plan) : plan;
|
|
792
|
+
const { id: _id, fingerprint: _fingerprint, ...unsigned } = acceptedPlan;
|
|
793
|
+
return { plan: acceptedPlan, hash: sha256(canonicalStringify(toJsonValue(unsigned))) };
|
|
675
794
|
}
|
|
676
795
|
catch {
|
|
677
796
|
throw invalidPersistedPlan();
|
|
@@ -809,10 +928,10 @@ function localAggregateInputQuery(query, aggregation, maxRows, primaryKey, optio
|
|
|
809
928
|
...base,
|
|
810
929
|
...(requiredFields.size > 0 ? { outFields: [...requiredFields].sort() } : {}),
|
|
811
930
|
...(!options.preserveGeometry ? { returnGeometry: false } : {}),
|
|
812
|
-
// The OGC
|
|
813
|
-
// N-row ceiling becomes an N + 1 wire request and is reported
|
|
814
|
-
// exceededTransferLimit. GeoServices retains its established
|
|
815
|
-
// sentinel in the canonical step for backward-compatible
|
|
931
|
+
// The OGC, OData, and WFS queryAll adapters own the overflow sentinel: a
|
|
932
|
+
// logical N-row ceiling becomes an N + 1 wire request and is reported
|
|
933
|
+
// through exceededTransferLimit. GeoServices retains its established
|
|
934
|
+
// explicit sentinel in the canonical step for backward-compatible output.
|
|
816
935
|
pagination: { offset: 0, limit: maxRows + (options.adapterOwnsLookahead ? 0 : 1) },
|
|
817
936
|
});
|
|
818
937
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic result-representation selection for the query planner.
|
|
3
|
+
*
|
|
4
|
+
* The planner's {@link QueryPlanExecutionMode} axis answers *when* rows are
|
|
5
|
+
* delivered (`snapshot` vs `delta`). This module adds the orthogonal axis that
|
|
6
|
+
* answers *what shape* they arrive in: protocol-neutral `Result` feature
|
|
7
|
+
* objects, or a `ColumnarBatchV1`.
|
|
8
|
+
*
|
|
9
|
+
* Selection is metadata-only and reproducible by inspection. It reads the
|
|
10
|
+
* declared source identity (protocol plus physical geometry encoding) and the
|
|
11
|
+
* caller's {@link QueryPlanningEstimates}; it never reads result data, never
|
|
12
|
+
* constructs a worker, and never performs I/O. Two callers who explain the same
|
|
13
|
+
* query against the same declared source get the same decision, and therefore
|
|
14
|
+
* the same plan fingerprint.
|
|
15
|
+
*
|
|
16
|
+
* Capability honesty is the governing rule: a source that cannot serve columnar
|
|
17
|
+
* plans `object` and records why, and an explicit unsatisfiable pin throws
|
|
18
|
+
* {@link HonuaCapabilityNotSupportedError}. There is no silent fallback that
|
|
19
|
+
* would report a representation execution cannot deliver.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { type DuckDbGeometryEncoding, type QueryIrV1, type QueryIrV2, type QueryPlanRepresentationDecisionV1, type QueryPlanRepresentationRequest, type QueryPlanningEstimates } from "./types.js";
|
|
24
|
+
/**
|
|
25
|
+
* Estimated rows at or above which `representation: "auto"` selects columnar.
|
|
26
|
+
*
|
|
27
|
+
* Set at the SDK's bounded-local materialization ceiling
|
|
28
|
+
* (`MAX_LOCAL_MATERIALIZATION_ROWS`): below it a result is small enough that
|
|
29
|
+
* feature objects stay affordable, at or above it the per-row object cost is
|
|
30
|
+
* exactly the problem a columnar batch exists to remove.
|
|
31
|
+
*/
|
|
32
|
+
export declare const COLUMNAR_REPRESENTATION_MIN_ROWS = 100000;
|
|
33
|
+
/** Estimated payload bytes at or above which `representation: "auto"` selects columnar. */
|
|
34
|
+
export declare const COLUMNAR_REPRESENTATION_MIN_BYTES: number;
|
|
35
|
+
/**
|
|
36
|
+
* Physical geometry encodings with an executable columnar producer today.
|
|
37
|
+
*
|
|
38
|
+
* These are the GeoParquet 1.1 native encodings that map 1:1 onto a Honua
|
|
39
|
+
* GeoArrow geometry kind, so a batch can be produced without inventing a
|
|
40
|
+
* geometry type. The multi-part encodings (`geoarrow-multipoint`,
|
|
41
|
+
* `geoarrow-multilinestring`, `geoarrow-multipolygon`) are deliberately absent:
|
|
42
|
+
* this SDK's dependency-free GeoArrow mapping has no multi-part kind, and
|
|
43
|
+
* re-labelling their parts as a single-part column would misreport the
|
|
44
|
+
* geometry.
|
|
45
|
+
*/
|
|
46
|
+
export declare const COLUMNAR_REPRESENTATION_ENCODINGS: readonly DuckDbGeometryEncoding[];
|
|
47
|
+
/** True for a declared geometry encoding that has an executable columnar producer. */
|
|
48
|
+
export declare function isColumnarRepresentationEncoding(encoding: DuckDbGeometryEncoding | undefined): encoding is DuckDbGeometryEncoding;
|
|
49
|
+
/** Validate a caller-supplied representation request; `undefined` means `auto`. */
|
|
50
|
+
export declare function normalizeRepresentationRequest(value: unknown): QueryPlanRepresentationRequest;
|
|
51
|
+
/**
|
|
52
|
+
* Select the plan's result representation.
|
|
53
|
+
*
|
|
54
|
+
* @throws HonuaCapabilityNotSupportedError when `requested` is `"columnar"` and
|
|
55
|
+
* the declared source or query cannot serve it. Failing closed here is the
|
|
56
|
+
* point: an unsatisfiable pin must never quietly become object execution.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createQueryPlanRepresentationDecision(ir: QueryIrV1 | QueryIrV2, estimates: QueryPlanningEstimates, requested: QueryPlanRepresentationRequest): QueryPlanRepresentationDecisionV1;
|
|
59
|
+
//# sourceMappingURL=representation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"representation.d.ts","sourceRoot":"","sources":["../../../src/query-planner/representation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAEL,KAAK,sBAAsB,EAK3B,KAAK,SAAS,EACd,KAAK,SAAS,EAEd,KAAK,iCAAiC,EAGtC,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC5B,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC,SAAU,CAAC;AAExD,2FAA2F;AAC3F,eAAO,MAAM,iCAAiC,QAAkB,CAAC;AAEjE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,EAAE,SAAS,sBAAsB,EAI7E,CAAC;AAEH,sFAAsF;AACtF,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,sBAAsB,GAAG,SAAS,GAC3C,QAAQ,IAAI,sBAAsB,CAEpC;AAED,mFAAmF;AACnF,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,8BAA8B,CAM7F;AA2CD;;;;;;GAMG;AACH,wBAAgB,qCAAqC,CACnD,EAAE,EAAE,SAAS,GAAG,SAAS,EACzB,SAAS,EAAE,sBAAsB,EACjC,SAAS,EAAE,8BAA8B,GACxC,iCAAiC,CA0BnC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic result-representation selection for the query planner.
|
|
3
|
+
*
|
|
4
|
+
* The planner's {@link QueryPlanExecutionMode} axis answers *when* rows are
|
|
5
|
+
* delivered (`snapshot` vs `delta`). This module adds the orthogonal axis that
|
|
6
|
+
* answers *what shape* they arrive in: protocol-neutral `Result` feature
|
|
7
|
+
* objects, or a `ColumnarBatchV1`.
|
|
8
|
+
*
|
|
9
|
+
* Selection is metadata-only and reproducible by inspection. It reads the
|
|
10
|
+
* declared source identity (protocol plus physical geometry encoding) and the
|
|
11
|
+
* caller's {@link QueryPlanningEstimates}; it never reads result data, never
|
|
12
|
+
* constructs a worker, and never performs I/O. Two callers who explain the same
|
|
13
|
+
* query against the same declared source get the same decision, and therefore
|
|
14
|
+
* the same plan fingerprint.
|
|
15
|
+
*
|
|
16
|
+
* Capability honesty is the governing rule: a source that cannot serve columnar
|
|
17
|
+
* plans `object` and records why, and an explicit unsatisfiable pin throws
|
|
18
|
+
* {@link HonuaCapabilityNotSupportedError}. There is no silent fallback that
|
|
19
|
+
* would report a representation execution cannot deliver.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { HonuaCapabilityNotSupportedError } from "../core/errors.js";
|
|
24
|
+
import { HonuaQueryPlanningError, QUERY_PLAN_DIAGNOSTICS_VERSION, } from "./types.js";
|
|
25
|
+
/**
|
|
26
|
+
* Estimated rows at or above which `representation: "auto"` selects columnar.
|
|
27
|
+
*
|
|
28
|
+
* Set at the SDK's bounded-local materialization ceiling
|
|
29
|
+
* (`MAX_LOCAL_MATERIALIZATION_ROWS`): below it a result is small enough that
|
|
30
|
+
* feature objects stay affordable, at or above it the per-row object cost is
|
|
31
|
+
* exactly the problem a columnar batch exists to remove.
|
|
32
|
+
*/
|
|
33
|
+
export const COLUMNAR_REPRESENTATION_MIN_ROWS = 100_000;
|
|
34
|
+
/** Estimated payload bytes at or above which `representation: "auto"` selects columnar. */
|
|
35
|
+
export const COLUMNAR_REPRESENTATION_MIN_BYTES = 8 * 1024 * 1024;
|
|
36
|
+
/**
|
|
37
|
+
* Physical geometry encodings with an executable columnar producer today.
|
|
38
|
+
*
|
|
39
|
+
* These are the GeoParquet 1.1 native encodings that map 1:1 onto a Honua
|
|
40
|
+
* GeoArrow geometry kind, so a batch can be produced without inventing a
|
|
41
|
+
* geometry type. The multi-part encodings (`geoarrow-multipoint`,
|
|
42
|
+
* `geoarrow-multilinestring`, `geoarrow-multipolygon`) are deliberately absent:
|
|
43
|
+
* this SDK's dependency-free GeoArrow mapping has no multi-part kind, and
|
|
44
|
+
* re-labelling their parts as a single-part column would misreport the
|
|
45
|
+
* geometry.
|
|
46
|
+
*/
|
|
47
|
+
export const COLUMNAR_REPRESENTATION_ENCODINGS = Object.freeze([
|
|
48
|
+
"geoarrow-linestring",
|
|
49
|
+
"geoarrow-point",
|
|
50
|
+
"geoarrow-polygon",
|
|
51
|
+
]);
|
|
52
|
+
/** True for a declared geometry encoding that has an executable columnar producer. */
|
|
53
|
+
export function isColumnarRepresentationEncoding(encoding) {
|
|
54
|
+
return encoding !== undefined && COLUMNAR_REPRESENTATION_ENCODINGS.includes(encoding);
|
|
55
|
+
}
|
|
56
|
+
/** Validate a caller-supplied representation request; `undefined` means `auto`. */
|
|
57
|
+
export function normalizeRepresentationRequest(value) {
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return "auto";
|
|
60
|
+
if (value !== "auto" && value !== "object" && value !== "columnar") {
|
|
61
|
+
throw new HonuaQueryPlanningError("invalid-query", "representation is invalid");
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function declaredGeometryEncoding(source) {
|
|
66
|
+
return source.protocol === "geoparquet" ? source.geoparquet?.geometryEncoding : undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Decide whether this source and query can be served columnar, from declared
|
|
70
|
+
* metadata alone. Reasons are ordered most-fundamental first so the recorded
|
|
71
|
+
* reason names the constraint a caller would have to change first.
|
|
72
|
+
*/
|
|
73
|
+
function columnarCapability(source, query) {
|
|
74
|
+
const geometryEncoding = declaredGeometryEncoding(source);
|
|
75
|
+
const encoded = geometryEncoding === undefined ? {} : { geometryEncoding };
|
|
76
|
+
if (source.protocol !== "geoparquet") {
|
|
77
|
+
return { supported: false, reason: "protocol-not-columnar", ...encoded };
|
|
78
|
+
}
|
|
79
|
+
if (!isColumnarRepresentationEncoding(geometryEncoding)) {
|
|
80
|
+
return { supported: false, reason: "encoding-not-columnar", ...encoded };
|
|
81
|
+
}
|
|
82
|
+
if (query.aggregation !== undefined) {
|
|
83
|
+
return { supported: false, reason: "aggregation-not-columnar", ...encoded };
|
|
84
|
+
}
|
|
85
|
+
if (query.returnGeometry === false) {
|
|
86
|
+
return { supported: false, reason: "geometry-not-requested", ...encoded };
|
|
87
|
+
}
|
|
88
|
+
return { supported: true, reason: "workload-above-threshold", ...encoded };
|
|
89
|
+
}
|
|
90
|
+
function meetsColumnarThreshold(estimates) {
|
|
91
|
+
if (estimates.rows !== undefined && estimates.rows >= COLUMNAR_REPRESENTATION_MIN_ROWS)
|
|
92
|
+
return true;
|
|
93
|
+
return estimates.bytes !== undefined && estimates.bytes >= COLUMNAR_REPRESENTATION_MIN_BYTES;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Select the plan's result representation.
|
|
97
|
+
*
|
|
98
|
+
* @throws HonuaCapabilityNotSupportedError when `requested` is `"columnar"` and
|
|
99
|
+
* the declared source or query cannot serve it. Failing closed here is the
|
|
100
|
+
* point: an unsatisfiable pin must never quietly become object execution.
|
|
101
|
+
*/
|
|
102
|
+
export function createQueryPlanRepresentationDecision(ir, estimates, requested) {
|
|
103
|
+
const capability = columnarCapability(ir.source, ir.query);
|
|
104
|
+
if (requested === "columnar" && !capability.supported) {
|
|
105
|
+
throw new HonuaCapabilityNotSupportedError("columnar-execution", ir.source.protocol, ir.source.id, {
|
|
106
|
+
context: { representation: "columnar", reason: capability.reason },
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const inputs = {
|
|
110
|
+
...(capability.geometryEncoding === undefined ? {} : { geometryEncoding: capability.geometryEncoding }),
|
|
111
|
+
rowThreshold: COLUMNAR_REPRESENTATION_MIN_ROWS,
|
|
112
|
+
byteThreshold: COLUMNAR_REPRESENTATION_MIN_BYTES,
|
|
113
|
+
...(estimates.rows === undefined ? {} : { estimatedRows: estimates.rows }),
|
|
114
|
+
...(estimates.bytes === undefined ? {} : { estimatedBytes: estimates.bytes }),
|
|
115
|
+
};
|
|
116
|
+
const available = capability.supported
|
|
117
|
+
? Object.freeze(["object", "columnar"])
|
|
118
|
+
: Object.freeze(["object"]);
|
|
119
|
+
const { selected, reason } = selectRepresentation(capability, estimates, requested);
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
version: QUERY_PLAN_DIAGNOSTICS_VERSION,
|
|
122
|
+
requested,
|
|
123
|
+
selected,
|
|
124
|
+
available,
|
|
125
|
+
reason,
|
|
126
|
+
inputs: Object.freeze(inputs),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function selectRepresentation(capability, estimates, requested) {
|
|
130
|
+
if (requested !== "auto")
|
|
131
|
+
return { selected: requested, reason: "explicit-pin" };
|
|
132
|
+
if (!capability.supported)
|
|
133
|
+
return { selected: "object", reason: capability.reason };
|
|
134
|
+
if (estimates.rows === undefined && estimates.bytes === undefined) {
|
|
135
|
+
return { selected: "object", reason: "estimate-unavailable" };
|
|
136
|
+
}
|
|
137
|
+
return meetsColumnarThreshold(estimates)
|
|
138
|
+
? { selected: "columnar", reason: "workload-above-threshold" }
|
|
139
|
+
: { selected: "object", reason: "workload-below-threshold" };
|
|
140
|
+
}
|