@fjader/svelte 0.2.2 → 0.2.4
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/README.md +2 -0
- package/dist/components/axes/AxisBottom.svelte +23 -6
- package/dist/components/axes/AxisBottom.svelte.d.ts +10 -0
- package/dist/components/axes/AxisLeft.svelte +77 -8
- package/dist/components/axes/AxisLeft.svelte.d.ts +17 -0
- package/dist/components/axes/BandAxisTicks.svelte +64 -25
- package/dist/components/axes/BandAxisTicks.svelte.d.ts +5 -5
- package/dist/components/axes/axis-title.d.ts +30 -0
- package/dist/components/axes/axis-title.js +77 -0
- package/dist/components/charts/ChartCell.svelte +1 -1
- package/dist/components/charts/ChartContainer.svelte +12 -3
- package/dist/components/charts/ChartContainer.svelte.d.ts +7 -0
- package/dist/components/guides/legend/ContinuousColorLegend.svelte +9 -2
- package/dist/components/guides/legend/OrdinalColorLegend.svelte +96 -35
- package/dist/components/marks/AreaMarkX.svelte +21 -6
- package/dist/components/marks/AreaMarkY.svelte +21 -6
- package/dist/components/marks/BarMarkX.svelte +16 -1
- package/dist/components/marks/BarMarkY.svelte +16 -1
- package/dist/components/marks/DotsMark.svelte +5 -0
- package/dist/components/marks/LineMark.svelte +8 -1
- package/dist/components/marks/ParallelCoordinatesMark.svelte +10 -0
- package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +5 -0
- package/dist/components/marks/RectMark.svelte +5 -0
- package/dist/components/marks/RectXMark.svelte +5 -0
- package/dist/components/marks/RectYMark.svelte +5 -0
- package/dist/components/marks/RuleMarkX.svelte +12 -2
- package/dist/components/marks/RuleMarkY.svelte +12 -2
- package/dist/components/marks/TextMark.svelte +76 -13
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +21 -2
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +300 -42
- package/dist/components/marks/lifecycle/domain-queries.d.ts +3 -1
- package/dist/components/marks/lifecycle/domain-queries.js +22 -15
- package/dist/components/marks/lifecycle/domain-registration.js +2 -6
- package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +104 -0
- package/dist/components/marks/lifecycle/other-rows.svelte.js +193 -0
- package/dist/components/marks/lifecycle/other-split.d.ts +80 -0
- package/dist/components/marks/lifecycle/other-split.js +112 -0
- package/dist/components/marks/lifecycle/query-builder.d.ts +26 -1
- package/dist/components/marks/lifecycle/query-builder.js +25 -5
- package/dist/components/marks/lifecycle/transform-scale-key.d.ts +16 -0
- package/dist/components/marks/lifecycle/transform-scale-key.js +27 -0
- package/dist/components/marks/utils/area-outline.d.ts +24 -0
- package/dist/components/marks/utils/area-outline.js +10 -0
- package/dist/components/marks/utils/reactive-config.d.ts +1 -1
- package/dist/components/marks/utils/reactive-config.js +9 -0
- package/dist/components/marks/utils/tooltip-helpers.d.ts +4 -1
- package/dist/components/marks/utils/tooltip-helpers.js +2 -1
- package/dist/components/rendering/Areas.svelte +30 -6
- package/dist/components/rendering/Areas.svelte.d.ts +2 -0
- package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +3 -1
- package/dist/components/rendering/BaseGPURenderer.svelte.js +8 -3
- package/dist/components/rendering/Dots.svelte +24 -3
- package/dist/components/rendering/Dots.svelte.d.ts +2 -0
- package/dist/components/rendering/GridLine.svelte +2 -1
- package/dist/components/rendering/Lines.svelte +49 -7
- package/dist/components/rendering/Lines.svelte.d.ts +9 -0
- package/dist/components/rendering/ParallelLines.svelte +26 -7
- package/dist/components/rendering/ParallelLines.svelte.d.ts +2 -0
- package/dist/components/rendering/Rects.svelte +129 -20
- package/dist/components/rendering/Rects.svelte.d.ts +9 -0
- package/dist/components/rendering/RuleX.svelte +20 -5
- package/dist/components/rendering/RuleX.svelte.d.ts +2 -0
- package/dist/components/rendering/RuleY.svelte +20 -5
- package/dist/components/rendering/RuleY.svelte.d.ts +2 -0
- package/dist/components/rendering/activity-reads.d.ts +111 -0
- package/dist/components/rendering/activity-reads.js +154 -0
- package/dist/components/rendering/uniform-sizes.d.ts +26 -0
- package/dist/components/rendering/uniform-sizes.js +28 -0
- package/dist/config/ChartConfig.svelte.d.ts +5 -1
- package/dist/gpu/ColorScaleShaderLib.d.ts +13 -1
- package/dist/gpu/ColorScaleShaderLib.js +56 -5
- package/dist/gpu/GPUCoordinator.svelte.d.ts +8 -0
- package/dist/gpu/GPUCoordinator.svelte.js +68 -32
- package/dist/gpu/MorphedChannels.d.ts +6 -32
- package/dist/gpu/MorphedChannels.js +45 -3
- package/dist/gpu/ResolveShader.d.ts +2 -2
- package/dist/gpu/ResolveShader.js +2 -2
- package/dist/gpu/ScaleTemplateSystem.d.ts +19 -0
- package/dist/gpu/ScaleTemplateSystem.js +111 -11
- package/dist/gpu/SizeScaleShaderLib.d.ts +4 -4
- package/dist/gpu/SizeScaleShaderLib.js +4 -4
- package/dist/gpu/identity-slot-packing.d.ts +157 -40
- package/dist/gpu/identity-slot-packing.js +262 -62
- package/dist/gpu/tracked-scales.d.ts +14 -0
- package/dist/gpu/tracked-scales.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/interactors/BandLabelToggle.svelte +5 -9
- package/dist/interactors/BandLabelToggle.svelte.d.ts +3 -3
- package/dist/interactors/DerivedInterval1D.svelte +26 -54
- package/dist/interactors/Interval1D.svelte +8 -2
- package/dist/interactors/Interval2D.svelte +8 -2
- package/dist/interactors/ParallelAxisSource.svelte +14 -5
- package/dist/interactors/ParallelAxisSource.svelte.d.ts +2 -1
- package/dist/interactors/ParallelBrushes.svelte +8 -9
- package/dist/interactors/ParallelBrushes.svelte.d.ts +3 -3
- package/dist/interactors/Toggle.svelte +48 -38
- package/dist/interactors/Toggle.svelte.d.ts +4 -3
- package/dist/interactors/Tooltip.svelte +140 -42
- package/dist/interactors/TooltipManager.svelte.d.ts +20 -5
- package/dist/interactors/TooltipManager.svelte.js +40 -10
- package/dist/interactors/other-tooltip.d.ts +16 -0
- package/dist/interactors/other-tooltip.js +22 -0
- package/dist/interactors/relay-clauses.d.ts +23 -0
- package/dist/interactors/relay-clauses.js +60 -0
- package/dist/query-engine/CrossfilterSelection.svelte.d.ts +88 -15
- package/dist/query-engine/CrossfilterSelection.svelte.js +94 -20
- package/dist/query-engine/FieldActivity.svelte.d.ts +128 -0
- package/dist/query-engine/FieldActivity.svelte.js +378 -0
- package/dist/query-engine/FilterGroup.svelte.d.ts +76 -30
- package/dist/query-engine/FilterGroup.svelte.js +223 -124
- package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +73 -3
- package/dist/query-engine/FilterGroupRegistry.svelte.js +161 -12
- package/dist/query-engine/MarkHandle.svelte.d.ts +9 -1
- package/dist/query-engine/MarkHandle.svelte.js +12 -2
- package/dist/query-engine/QueryEngine.d.ts +52 -16
- package/dist/query-engine/QueryEngine.js +116 -34
- package/dist/query-engine/categorical-identity.d.ts +114 -27
- package/dist/query-engine/categorical-identity.js +225 -51
- package/dist/query-engine/index.d.ts +2 -1
- package/dist/query-engine/index.js +1 -0
- package/dist/query-engine/preagg/Preagg.d.ts +1 -1
- package/dist/query-engine/preagg/Preagg.js +11 -9
- package/dist/query-engine/preagg/plan.d.ts +14 -5
- package/dist/query-engine/preagg/plan.js +44 -18
- package/dist/query-engine/toggle-predicate.d.ts +21 -0
- package/dist/query-engine/toggle-predicate.js +41 -2
- package/dist/scales/ActivityTable.d.ts +103 -0
- package/dist/scales/ActivityTable.js +220 -0
- package/dist/scales/BandScaleState.svelte.d.ts +41 -15
- package/dist/scales/BandScaleState.svelte.js +78 -36
- package/dist/scales/ChartScales.svelte.d.ts +2 -2
- package/dist/scales/ColorScaleState.svelte.d.ts +36 -8
- package/dist/scales/ColorScaleState.svelte.js +118 -24
- package/dist/scales/DomainResolver.d.ts +2 -0
- package/dist/scales/DomainResolver.js +15 -8
- package/dist/scales/ScaleRegistry.svelte.d.ts +9 -0
- package/dist/scales/ScaleRegistry.svelte.js +22 -1
- package/dist/scales/TimeScaleState.svelte.js +7 -1
- package/dist/spec/index.js +1 -0
- package/dist/spec/types.d.ts +4 -0
- package/dist/theme.css +5 -4
- package/dist/transforms/TransformCompiler.d.ts +37 -2
- package/dist/transforms/TransformCompiler.js +44 -1
- package/dist/transforms/index.d.ts +27 -0
- package/dist/transforms/index.js +127 -19
- package/dist/types.d.ts +71 -0
- package/package.json +3 -2
|
@@ -11,14 +11,16 @@
|
|
|
11
11
|
* later tables APPEND unseen values with continuing ids. It never reorders and
|
|
12
12
|
* never renumbers, so an id already baked into a vertex buffer stays valid and
|
|
13
13
|
* no earlier mark has to requery. `value` carries the source column's own type
|
|
14
|
-
* and `label` is NULL, except on a synthetic row (`
|
|
15
|
-
* that is inverted.
|
|
14
|
+
* and `label` is NULL, except on a synthetic row (`acquireSynthetic`), where
|
|
15
|
+
* that is inverted, and on the NULL category's row, where both are NULL.
|
|
16
16
|
*
|
|
17
17
|
* 2. A **per-table view**, `<table>_v_s<session>`, selecting `*` plus one
|
|
18
18
|
* `__dplot_<field>_idx` column per requested field. Always LEFT JOIN with
|
|
19
19
|
* COALESCE(-1): an inner join silently drops rows whose value is not in the
|
|
20
|
-
* dim (
|
|
21
|
-
* rendering bug. -1 is the unmapped sentinel, unified in `1ac4fd4`.
|
|
20
|
+
* dim (a value a later append will add), which is data loss presenting as a
|
|
21
|
+
* rendering bug. -1 is the unmapped sentinel, unified in `1ac4fd4`. A NULL
|
|
22
|
+
* value is not unmapped: the dim has a row for it (value NULL, label NULL),
|
|
23
|
+
* joined with IS NOT DISTINCT FROM, so NULL is a category like any other.
|
|
22
24
|
*
|
|
23
25
|
* 3. An **additive coalescer**, `ensureColumns()`. Concurrent callers union into
|
|
24
26
|
* a pending set, one CREATE OR REPLACE VIEW runs with the full union, and a
|
|
@@ -46,6 +48,16 @@ import { IDENTITY_SLOT_CAPACITY } from '../gpu/identity-slot-packing';
|
|
|
46
48
|
function asField(field) {
|
|
47
49
|
return typeof field === 'string' ? { key: field, sql: quote(field) } : field;
|
|
48
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* How a category value is shown: as itself, except the NULL category, which has
|
|
53
|
+
* no text of its own. The one formatter for axis ticks, legend entries, facet
|
|
54
|
+
* labels and tooltips, so NULL reads the same everywhere. Selections keep the
|
|
55
|
+
* raw `null`: this is display only.
|
|
56
|
+
*/
|
|
57
|
+
export const NULL_CATEGORY_LABEL = '(null)';
|
|
58
|
+
export function categoryLabel(value) {
|
|
59
|
+
return value === null ? NULL_CATEGORY_LABEL : value;
|
|
60
|
+
}
|
|
49
61
|
/**
|
|
50
62
|
* A field's id <-> value bijection, both directions in one object.
|
|
51
63
|
*
|
|
@@ -61,11 +73,33 @@ function asField(field) {
|
|
|
61
73
|
* once and stay correct when a second table appends to the dim.
|
|
62
74
|
*/
|
|
63
75
|
export class FieldIdentity {
|
|
64
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* id -> source value. Dense and 0-based, so position is the id. A synthetic
|
|
78
|
+
* id (an Other bucket) holds its display label here.
|
|
79
|
+
*/
|
|
65
80
|
values = [];
|
|
66
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* source value -> id, for real values only: the inverse of `values` with the
|
|
83
|
+
* synthetic ids left out. A label is not a value, and several synthetic ids
|
|
84
|
+
* may share one, so nothing resolves a bucket by its label.
|
|
85
|
+
*/
|
|
67
86
|
ids = new Map();
|
|
68
|
-
|
|
87
|
+
/** The synthetic ids (Other buckets): rows of the dim no source row joins to. */
|
|
88
|
+
synthetic = new Set();
|
|
89
|
+
/**
|
|
90
|
+
* The NULL category's id, or null while the data has no NULL. An ordinary
|
|
91
|
+
* category: ranked, drawn, selectable; `values[nullId]` is `null`, and `ids`
|
|
92
|
+
* maps `null` to it, so value lookups need no special case.
|
|
93
|
+
*/
|
|
94
|
+
nullId = null;
|
|
95
|
+
/**
|
|
96
|
+
* The identity key this is the bijection for, as `identityKeyFor` derives
|
|
97
|
+
* it: what a scale that adopted it is keyed by for selection activity.
|
|
98
|
+
* Empty for one built outside the engine, as tests do.
|
|
99
|
+
*/
|
|
100
|
+
key;
|
|
101
|
+
constructor(values, key = '') {
|
|
102
|
+
this.key = key;
|
|
69
103
|
if (values)
|
|
70
104
|
this.reset(values);
|
|
71
105
|
}
|
|
@@ -76,14 +110,26 @@ export class FieldIdentity {
|
|
|
76
110
|
* comparing `values.length` is not.
|
|
77
111
|
*/
|
|
78
112
|
version = 0;
|
|
79
|
-
/**
|
|
80
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Replace both directions. The only writer. `synthetic` names the ids that
|
|
115
|
+
* are buckets rather than values: they keep their label in `values` and stay
|
|
116
|
+
* out of `ids`.
|
|
117
|
+
*/
|
|
118
|
+
reset(values, synthetic = []) {
|
|
81
119
|
this.version++;
|
|
82
120
|
this.values.length = 0;
|
|
83
121
|
this.ids.clear();
|
|
122
|
+
this.synthetic.clear();
|
|
123
|
+
this.nullId = null;
|
|
124
|
+
for (const id of synthetic)
|
|
125
|
+
this.synthetic.add(id);
|
|
84
126
|
for (let i = 0; i < values.length; i++) {
|
|
85
127
|
this.values.push(values[i]);
|
|
128
|
+
if (this.synthetic.has(i))
|
|
129
|
+
continue;
|
|
86
130
|
this.ids.set(values[i], i);
|
|
131
|
+
if (values[i] === null)
|
|
132
|
+
this.nullId = i;
|
|
87
133
|
}
|
|
88
134
|
}
|
|
89
135
|
/** True once the dim has been built and holds at least one value. */
|
|
@@ -130,10 +176,11 @@ function identityToken(field) {
|
|
|
130
176
|
return sanitized === field ? field : `h${shortHash(field)}`;
|
|
131
177
|
}
|
|
132
178
|
/**
|
|
133
|
-
* Above this many distinct values a band axis is refused outright.
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
179
|
+
* Above this many distinct values a band axis is refused outright. The shared
|
|
180
|
+
* slot buffer (IDENTITY_SLOT_CAPACITY floats) holds every band and point
|
|
181
|
+
* scale's slots and every selected field's activity, one run per filter group,
|
|
182
|
+
* so an axis this large would crowd out the rest of the page — the limit is
|
|
183
|
+
* capacity, not taste.
|
|
137
184
|
*/
|
|
138
185
|
export const MAX_IDENTITY_CARDINALITY = 10000;
|
|
139
186
|
/**
|
|
@@ -201,6 +248,12 @@ export class CategoricalIdentity {
|
|
|
201
248
|
* flight.
|
|
202
249
|
*/
|
|
203
250
|
#identities = new Map();
|
|
251
|
+
/**
|
|
252
|
+
* Released synthetic ids, per field and label, for the next acquire. Keeps the
|
|
253
|
+
* dim from growing a row per remount: mark ids are random, so nothing about a
|
|
254
|
+
* remounted mark can name the id it held before. Cleared with the dims.
|
|
255
|
+
*/
|
|
256
|
+
#freeSynthetic = new Map();
|
|
204
257
|
/** key -> SQL producing the value. Set at registration, read by the drain. */
|
|
205
258
|
#sqlFor = new Map();
|
|
206
259
|
constructor(exec, session) {
|
|
@@ -216,9 +269,9 @@ export class CategoricalIdentity {
|
|
|
216
269
|
return this.#sqlFor.get(field);
|
|
217
270
|
}
|
|
218
271
|
/**
|
|
219
|
-
* Whether `field`'s dim has folded in `table`, so every
|
|
220
|
-
*
|
|
221
|
-
*
|
|
272
|
+
* Whether `field`'s dim has folded in `table`, so every value the table holds,
|
|
273
|
+
* NULL included, has an id and `-1` never appears. A dim another table built
|
|
274
|
+
* may lack this table's values; a refused dim covers nothing.
|
|
222
275
|
*/
|
|
223
276
|
dimCovers(field, table) {
|
|
224
277
|
const dim = this.#dims.get(field);
|
|
@@ -239,6 +292,31 @@ export class CategoricalIdentity {
|
|
|
239
292
|
dimTable(field) {
|
|
240
293
|
return `__dplot_dim_${identityToken(field)}_s${this.#session}`;
|
|
241
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* The table of ids `table` holds for `field`, or null before that table has
|
|
297
|
+
* folded into the dim. What `dense()` pads from, so a chart never pads a
|
|
298
|
+
* category only another table has.
|
|
299
|
+
*/
|
|
300
|
+
presentTable(field, table) {
|
|
301
|
+
return this.#dims.get(field)?.present.get(table)?.name ?? null;
|
|
302
|
+
}
|
|
303
|
+
/** The ids `table` holds for `field`, or null before that table has folded in. */
|
|
304
|
+
presentIds(field, table) {
|
|
305
|
+
return this.#dims.get(field)?.present.get(table)?.ids ?? null;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Record which of the dim's ids `table` holds: the ids of its distinct values,
|
|
309
|
+
* joined as the view joins (NULL to a NULL row, never to a synthetic one).
|
|
310
|
+
* Once per table, when it folds in.
|
|
311
|
+
*/
|
|
312
|
+
async #recordPresent(dim, table, expr, src) {
|
|
313
|
+
const name = `${dim.name}_t_${identityToken(table)}`;
|
|
314
|
+
await this.#exec.exec(`CREATE OR REPLACE TABLE ${quote(name)} AS ` +
|
|
315
|
+
`SELECT DISTINCT d.id FROM (SELECT DISTINCT ${expr} AS v FROM ${src}) s ` +
|
|
316
|
+
`JOIN ${quote(dim.name)} d ON s.v IS NOT DISTINCT FROM d.value AND d.label IS NULL`);
|
|
317
|
+
const rows = await this.#exec.queryRows(`SELECT id FROM ${quote(name)}`);
|
|
318
|
+
dim.present.set(table, { name, ids: new Set(rows.map((r) => Number(r.id))) });
|
|
319
|
+
}
|
|
242
320
|
/** Name of the view that carries id columns for `table`. */
|
|
243
321
|
viewTable(table) {
|
|
244
322
|
return `${identityToken(table)}_v_s${this.#session}`;
|
|
@@ -277,11 +355,19 @@ export class CategoricalIdentity {
|
|
|
277
355
|
identity(field) {
|
|
278
356
|
let entry = this.#identities.get(field);
|
|
279
357
|
if (!entry) {
|
|
280
|
-
entry = new FieldIdentity();
|
|
358
|
+
entry = new FieldIdentity(undefined, field);
|
|
281
359
|
this.#identities.set(field, entry);
|
|
282
360
|
}
|
|
283
361
|
return entry;
|
|
284
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* The field's current bijection, or null if nothing has asked for it since
|
|
365
|
+
* the last reset. Unlike `identity`, never creates one: for a holder checking
|
|
366
|
+
* whether the identity it applied is still the live one, on every frame.
|
|
367
|
+
*/
|
|
368
|
+
peekIdentity(field) {
|
|
369
|
+
return this.#identities.get(field) ?? null;
|
|
370
|
+
}
|
|
285
371
|
/** Fields the view for `table` currently exposes. Empty when there is no view. */
|
|
286
372
|
columnsFor(table) {
|
|
287
373
|
return [...(this.#views.get(table)?.current ?? [])];
|
|
@@ -428,7 +514,8 @@ export class CategoricalIdentity {
|
|
|
428
514
|
sql: this.#sqlFor.get(field) ?? quote(field),
|
|
429
515
|
tables: new Set(),
|
|
430
516
|
refusal: null,
|
|
431
|
-
chain: Promise.resolve()
|
|
517
|
+
chain: Promise.resolve(),
|
|
518
|
+
present: new Map()
|
|
432
519
|
};
|
|
433
520
|
this.#dims.set(field, dim);
|
|
434
521
|
}
|
|
@@ -441,38 +528,84 @@ export class CategoricalIdentity {
|
|
|
441
528
|
return next;
|
|
442
529
|
}
|
|
443
530
|
/**
|
|
444
|
-
*
|
|
445
|
-
* bucket a `topN({ includeOthers })` produces
|
|
531
|
+
* Acquire an id for a value that exists in no table — the synthetic "Other"
|
|
532
|
+
* bucket a `topN({ includeOthers })` produces — and return it with the label
|
|
533
|
+
* it displays.
|
|
446
534
|
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
535
|
+
* A released id with the same label is reused before a dim row is inserted,
|
|
536
|
+
* so remounting a chart does not grow the dim or the field's activity block.
|
|
537
|
+
* Keyed by label too, so a reused id never needs relabelling.
|
|
538
|
+
*
|
|
539
|
+
* Goes through the same writer as every other id, so `values(f)[id]` is the
|
|
540
|
+
* label; `synthetic` records that it is a bucket, which keeps it out of the
|
|
541
|
+
* value -> id map. Serialized on the per-field chain, so it cannot interleave
|
|
542
|
+
* with an append.
|
|
450
543
|
*
|
|
451
544
|
* Non-negative by construction: `scaleBand` discards `value < 0.0` before the
|
|
452
545
|
* slot lookup, so a negative sentinel could never render.
|
|
453
546
|
*
|
|
454
|
-
* **
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
* into rather than append beside.
|
|
547
|
+
* **A real category carrying the label stays a category.** The bucket takes
|
|
548
|
+
* `"<label> (rest)"` instead, so two bars never share a label and the real
|
|
549
|
+
* one stays selectable. The check runs at acquire; a reload re-acquires.
|
|
458
550
|
*
|
|
459
551
|
* Requires the dim to exist: call after `ensureColumns` has resolved.
|
|
460
552
|
*/
|
|
461
|
-
|
|
553
|
+
acquireSynthetic(field, label) {
|
|
462
554
|
const dim = this.#dims.get(field);
|
|
463
555
|
if (!dim) {
|
|
464
|
-
return Promise.reject(new Error(`No dim for "${field}" —
|
|
556
|
+
return Promise.reject(new Error(`No dim for "${field}" — acquireSynthetic must follow ensureColumns.`));
|
|
465
557
|
}
|
|
466
|
-
const
|
|
558
|
+
const generation = this.#generation;
|
|
559
|
+
const next = dim.chain.then(() => this.#acquireSyntheticNow(dim, field, label, generation));
|
|
467
560
|
// Drop both the value and any rejection: the chain only sequences work,
|
|
468
561
|
// and one failure must not poison every later append.
|
|
469
562
|
dim.chain = next.then(() => { }, () => { });
|
|
470
563
|
return next;
|
|
471
564
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
565
|
+
/**
|
|
566
|
+
* Give an acquired id back for the next acquire of the same label. One from
|
|
567
|
+
* before a reset names a row of a dropped dim, and is ignored.
|
|
568
|
+
*/
|
|
569
|
+
releaseSynthetic(synthetic) {
|
|
570
|
+
if (synthetic.generation !== this.#generation)
|
|
571
|
+
return;
|
|
572
|
+
let byLabel = this.#freeSynthetic.get(synthetic.field);
|
|
573
|
+
if (!byLabel)
|
|
574
|
+
this.#freeSynthetic.set(synthetic.field, (byLabel = new Map()));
|
|
575
|
+
let free = byLabel.get(synthetic.label);
|
|
576
|
+
if (!free)
|
|
577
|
+
byLabel.set(synthetic.label, (free = []));
|
|
578
|
+
if (!free.includes(synthetic.id))
|
|
579
|
+
free.push(synthetic.id);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Abort an acquire that a reset overtook. An AbortError, which a mark's setup
|
|
583
|
+
* treats as superseded rather than failed: the reset re-runs that setup, and
|
|
584
|
+
* it acquires again from the new dim.
|
|
585
|
+
*/
|
|
586
|
+
#throwIfSuperseded(generation, field, label) {
|
|
587
|
+
if (this.#generation === generation)
|
|
588
|
+
return;
|
|
589
|
+
const superseded = new Error(`Acquiring "${label}" for "${field}" was superseded by a reset.`);
|
|
590
|
+
superseded.name = 'AbortError';
|
|
591
|
+
throw superseded;
|
|
592
|
+
}
|
|
593
|
+
async #acquireSyntheticNow(dim, field, requested, generation) {
|
|
594
|
+
// Queued behind work that outlived a reset, as #ensureDimNow checks too.
|
|
595
|
+
// Without this the INSERT would write a bucket row into the NEW dim (same
|
|
596
|
+
// name) that no one holds and no release can return: releases from an
|
|
597
|
+
// older generation are ignored.
|
|
598
|
+
// Checked again after each await: a reset landing mid-flight drops the dim
|
|
599
|
+
// the row went into, and the identity read afterwards is a new one.
|
|
600
|
+
this.#throwIfSuperseded(generation, field, requested);
|
|
601
|
+
const identity = this.identity(field);
|
|
602
|
+
let label = requested;
|
|
603
|
+
while (identity.ids.has(label))
|
|
604
|
+
label = `${label} (rest)`;
|
|
605
|
+
const free = this.#freeSynthetic.get(field)?.get(label);
|
|
606
|
+
const reused = free?.pop();
|
|
607
|
+
if (reused !== undefined)
|
|
608
|
+
return { field, id: reused, label, generation };
|
|
476
609
|
const dimName = quote(dim.name);
|
|
477
610
|
// value NULL, label carrying the text. The label cannot go in `value`:
|
|
478
611
|
// that column has the source column's type, so 'Other' against a year or
|
|
@@ -484,15 +617,21 @@ export class CategoricalIdentity {
|
|
|
484
617
|
// joins ON t.key = d.value. Any in-range sentinel would collide with a
|
|
485
618
|
// real category the moment the data contained it; NULL cannot, since
|
|
486
619
|
// NULL = anything is never true.
|
|
487
|
-
|
|
620
|
+
//
|
|
621
|
+
// RETURNING, not a lookup by label afterwards: several buckets may carry
|
|
622
|
+
// one label, and labels are kept out of the value -> id map.
|
|
623
|
+
const rows = await this.#exec.queryRows(`INSERT INTO ${dimName} (value, label, id) ` +
|
|
488
624
|
`SELECT NULL, ${literal(label)}, ` +
|
|
489
|
-
`(SELECT COALESCE(MAX(id), -1) + 1 FROM ${dimName})::INT`
|
|
625
|
+
`(SELECT COALESCE(MAX(id), -1) + 1 FROM ${dimName})::INT ` +
|
|
626
|
+
`RETURNING id`);
|
|
627
|
+
this.#throwIfSuperseded(generation, field, requested);
|
|
490
628
|
await this.#refreshValues(field);
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
629
|
+
this.#throwIfSuperseded(generation, field, requested);
|
|
630
|
+
const id = Number(rows[0]?.id);
|
|
631
|
+
if (!Number.isInteger(id) || !identity.synthetic.has(id)) {
|
|
632
|
+
throw new Error(`Acquired "${label}" for "${field}" but it is absent from the dim.`);
|
|
494
633
|
}
|
|
495
|
-
return id;
|
|
634
|
+
return { field, id, label, generation };
|
|
496
635
|
}
|
|
497
636
|
async #ensureDimNow(dim, field, table, generation) {
|
|
498
637
|
// BEFORE the tables check, not after: a mark whose setup re-runs would
|
|
@@ -520,8 +659,8 @@ export class CategoricalIdentity {
|
|
|
520
659
|
// that column is a conversion error, not a fallback.
|
|
521
660
|
await this.#exec.exec(`CREATE TABLE IF NOT EXISTS ${dimName} AS ` +
|
|
522
661
|
`SELECT value, NULL::VARCHAR AS label, ` +
|
|
523
|
-
`(ROW_NUMBER() OVER (ORDER BY value) - 1)::INT AS id ` +
|
|
524
|
-
`FROM (SELECT DISTINCT ${expr} AS value FROM ${src}
|
|
662
|
+
`(ROW_NUMBER() OVER (ORDER BY value NULLS LAST) - 1)::INT AS id ` +
|
|
663
|
+
`FROM (SELECT DISTINCT ${expr} AS value FROM ${src})`);
|
|
525
664
|
// A second statement: CTAS cannot carry a constraint. `value` is unique
|
|
526
665
|
// by construction here (DISTINCT) and on append (WHERE NOT EXISTS), but
|
|
527
666
|
// reserveSynthetic is a writer that could break it, and a duplicate
|
|
@@ -539,12 +678,18 @@ export class CategoricalIdentity {
|
|
|
539
678
|
// a positional INSERT would silently put the id into `label`.
|
|
540
679
|
await this.#exec.exec(`INSERT INTO ${dimName} (value, label, id) ` +
|
|
541
680
|
`SELECT s.v, NULL, ((SELECT COALESCE(MAX(id), -1) FROM ${dimName}) + ` +
|
|
542
|
-
`ROW_NUMBER() OVER (ORDER BY s.v))::INT ` +
|
|
543
|
-
`FROM (SELECT DISTINCT ${expr} AS v FROM ${src}
|
|
544
|
-
|
|
681
|
+
`ROW_NUMBER() OVER (ORDER BY s.v NULLS LAST))::INT ` +
|
|
682
|
+
`FROM (SELECT DISTINCT ${expr} AS v FROM ${src}) s ` +
|
|
683
|
+
// IS NOT DISTINCT FROM, so a second table's NULLs find the NULL row
|
|
684
|
+
// (NULL = NULL is never true, and `=` appended one per table). The
|
|
685
|
+
// label test keeps a synthetic row, also value NULL, from passing
|
|
686
|
+
// for it.
|
|
687
|
+
`WHERE NOT EXISTS (SELECT 1 FROM ${dimName} d ` +
|
|
688
|
+
`WHERE d.value IS NOT DISTINCT FROM s.v AND d.label IS NULL)`);
|
|
545
689
|
}
|
|
546
690
|
dim.tables.add(table);
|
|
547
691
|
await this.#refreshValues(field);
|
|
692
|
+
await this.#recordPresent(dim, table, expr, src);
|
|
548
693
|
// The gate above saw ONE table. The dim spans every table that registers
|
|
549
694
|
// the field, so two tables of 6k each pass it individually and produce a
|
|
550
695
|
// 12k dim — and the limit is a property of the dim, since that is what
|
|
@@ -630,6 +775,16 @@ export class CategoricalIdentity {
|
|
|
630
775
|
* a full read cannot drift out of sync with the ids the way an incremental
|
|
631
776
|
* one can if an append is ever missed.
|
|
632
777
|
*/
|
|
778
|
+
/**
|
|
779
|
+
* Bumped whenever a dim's rows change: a table folding in, an Other bucket
|
|
780
|
+
* reserved, a reset. A query that reads a dim (dense padding, an id join)
|
|
781
|
+
* returns different rows afterwards under the same SQL text, so whatever
|
|
782
|
+
* caches results by that text must also key on this. Only ever increases.
|
|
783
|
+
*/
|
|
784
|
+
get version() {
|
|
785
|
+
return this.#version;
|
|
786
|
+
}
|
|
787
|
+
#version = 0;
|
|
633
788
|
async #refreshValues(field) {
|
|
634
789
|
const rows = await this.#exec.queryRows(`SELECT id, value, label FROM ${quote(this.dimTable(field))} ORDER BY id`);
|
|
635
790
|
// Ordered by id and dense by construction, so position is the id — and
|
|
@@ -638,10 +793,18 @@ export class CategoricalIdentity {
|
|
|
638
793
|
//
|
|
639
794
|
// `label` wins where it is set, which is only ever a synthetic row. Every
|
|
640
795
|
// real row has label NULL and keeps its native value, so this does not
|
|
641
|
-
// stringify anything the dim read from the source (see DimValue).
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
|
|
796
|
+
// stringify anything the dim read from the source (see DimValue). A row
|
|
797
|
+
// with a label is recorded as synthetic, which keeps it out of the
|
|
798
|
+
// value -> id map: a label is not a value.
|
|
799
|
+
const synthetic = [];
|
|
800
|
+
for (let i = 0; i < rows.length; i++)
|
|
801
|
+
if (rows[i].label != null)
|
|
802
|
+
synthetic.push(i);
|
|
803
|
+
this.identity(field).reset(rows.map((row) => (row.label ?? row.value)), synthetic);
|
|
804
|
+
// After the rows are written, never before: a query keyed on the new
|
|
805
|
+
// version must see them. One keyed on the old version may also see them,
|
|
806
|
+
// which costs a re-run later, not a stale result.
|
|
807
|
+
this.#version++;
|
|
645
808
|
}
|
|
646
809
|
/** CREATE OR REPLACE the view with an id column for every field in `all`. */
|
|
647
810
|
async #createView(table, all) {
|
|
@@ -657,7 +820,12 @@ export class CategoricalIdentity {
|
|
|
657
820
|
keyExprs.push(`(${this.#dims.get(field).sql}) AS ${quote(keyCol)}`);
|
|
658
821
|
keyCols.push(quote(keyCol));
|
|
659
822
|
columns.push(`COALESCE(${alias}.id, -1) AS ${quote(this.idColumn(field))}`);
|
|
660
|
-
|
|
823
|
+
// IS NOT DISTINCT FROM, so a NULL source value joins the NULL row; the
|
|
824
|
+
// label test keeps synthetic rows (also value NULL) out. Measured free:
|
|
825
|
+
// it keeps the hash join. An `= OR (both NULL)` form falls back to a
|
|
826
|
+
// nested loop, 73 s on 2 M rows x 10 000 categories — never write it.
|
|
827
|
+
joins.push(`LEFT JOIN ${dimName} ${alias} ON t.${quote(keyCol)} IS NOT DISTINCT FROM ${alias}.value ` +
|
|
828
|
+
`AND ${alias}.label IS NULL`);
|
|
661
829
|
});
|
|
662
830
|
// The value expression is computed in an inner SELECT, where only the
|
|
663
831
|
// source table is in scope, and joined on the qualified helper column.
|
|
@@ -691,14 +859,20 @@ export class CategoricalIdentity {
|
|
|
691
859
|
// Before any await: work already in flight compares against this and
|
|
692
860
|
// stops rather than re-creating what is about to be dropped.
|
|
693
861
|
this.#generation++;
|
|
862
|
+
this.#version++;
|
|
694
863
|
const views = [...this.#views.keys()].map((t) => this.viewTable(t));
|
|
695
|
-
const dims = [...this.#dims.values()].
|
|
864
|
+
const dims = [...this.#dims.values()].flatMap((d) => [
|
|
865
|
+
d.name,
|
|
866
|
+
...[...d.present.values()].map((p) => p.name)
|
|
867
|
+
]);
|
|
696
868
|
this.#views.clear();
|
|
697
869
|
this.#dims.clear();
|
|
698
870
|
// In place, so any scale still holding the reference empties with it.
|
|
699
871
|
for (const identity of this.#identities.values())
|
|
700
872
|
identity.reset([]);
|
|
701
873
|
this.#identities.clear();
|
|
874
|
+
// Their ids name rows of the dims being dropped.
|
|
875
|
+
this.#freeSynthetic.clear();
|
|
702
876
|
for (const view of views) {
|
|
703
877
|
await this.#exec.exec(`DROP VIEW IF EXISTS ${quote(view)}`);
|
|
704
878
|
}
|
|
@@ -41,6 +41,7 @@ export { MarkHandle } from './MarkHandle.svelte';
|
|
|
41
41
|
export type { QueryParams } from './MarkHandle.svelte';
|
|
42
42
|
export { FilterGroupRegistry } from './FilterGroupRegistry.svelte';
|
|
43
43
|
export { FilterGroup } from './FilterGroup.svelte';
|
|
44
|
-
export type { SourceOpts, SourceHandle, ToggleHandle, FilterItem
|
|
44
|
+
export type { SourceOpts, SourceHandle, ToggleHandle, FilterItem } from './FilterGroup.svelte';
|
|
45
|
+
export { FieldActivity } from './FieldActivity.svelte';
|
|
45
46
|
export { CrossfilterSelection } from './CrossfilterSelection.svelte';
|
|
46
47
|
export type { FilterExpr, FilterSchema, Clause, ClauseEntry } from './CrossfilterSelection.svelte';
|
|
@@ -47,4 +47,5 @@ export { mapSqlTypeToJs, buildSchemaMap } from './QueryEngine';
|
|
|
47
47
|
export { MarkHandle } from './MarkHandle.svelte';
|
|
48
48
|
export { FilterGroupRegistry } from './FilterGroupRegistry.svelte';
|
|
49
49
|
export { FilterGroup } from './FilterGroup.svelte';
|
|
50
|
+
export { FieldActivity } from './FieldActivity.svelte';
|
|
50
51
|
export { CrossfilterSelection } from './CrossfilterSelection.svelte';
|
|
@@ -59,7 +59,7 @@ export declare class Preagg {
|
|
|
59
59
|
*
|
|
60
60
|
* The source is named by its ID rather than inferred: before the first drag
|
|
61
61
|
* it has no clause yet, and other clauses on the same chart (a Toggle)
|
|
62
|
-
*
|
|
62
|
+
* skip the same chart a brush does.
|
|
63
63
|
*/
|
|
64
64
|
activate(filterGroup: FilterGroup, source: ActivationSource): Promise<void>;
|
|
65
65
|
/**
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* 3-Clause License; see NOTICE.
|
|
16
16
|
*/
|
|
17
17
|
import { binsFineEnough } from '../sufficient-statistics';
|
|
18
|
+
import { NO_ROUTE } from '../CrossfilterSelection.svelte';
|
|
18
19
|
import { planPreagg, preaggSelectSQL, preaggQuerySQL, bakedClausesHold, markKeys, estimateGroups, activeDimsKeyOf, binFitsView, fnvHash } from './plan';
|
|
19
20
|
import { BuildQueue } from './BuildQueue';
|
|
20
21
|
/**
|
|
@@ -95,10 +96,10 @@ export class Preagg {
|
|
|
95
96
|
*
|
|
96
97
|
* The source is named by its ID rather than inferred: before the first drag
|
|
97
98
|
* it has no clause yet, and other clauses on the same chart (a Toggle)
|
|
98
|
-
*
|
|
99
|
+
* skip the same chart a brush does.
|
|
99
100
|
*/
|
|
100
101
|
async activate(filterGroup, source) {
|
|
101
|
-
const { sourceId: activeSourceId,
|
|
102
|
+
const { sourceId: activeSourceId, categoricalKey, dims } = source;
|
|
102
103
|
if (this.#destroyed || dims.length === 0)
|
|
103
104
|
return;
|
|
104
105
|
// An activation outside batch() (a legend's hover) is a batch of one.
|
|
@@ -111,7 +112,8 @@ export class Preagg {
|
|
|
111
112
|
continue;
|
|
112
113
|
// A mark the source never filters gets no table for it, and keeps
|
|
113
114
|
// the tables it holds for other sources.
|
|
114
|
-
|
|
115
|
+
const route = reg.descriptor.route ?? NO_ROUTE;
|
|
116
|
+
if (!selection.filters(route, activeSourceId, categoricalKey))
|
|
115
117
|
continue;
|
|
116
118
|
const own = this.#of(markId);
|
|
117
119
|
// Skip if the latest build, finished or in flight, still matches
|
|
@@ -122,7 +124,7 @@ export class Preagg {
|
|
|
122
124
|
basis.activeSourceId === activeSourceId &&
|
|
123
125
|
basis.dimsKey === dimsKey &&
|
|
124
126
|
this.#binsFitDims(latest, dims) &&
|
|
125
|
-
bakedClausesHold(
|
|
127
|
+
bakedClausesHold(route, selection, latest)) {
|
|
126
128
|
continue;
|
|
127
129
|
}
|
|
128
130
|
}
|
|
@@ -136,8 +138,8 @@ export class Preagg {
|
|
|
136
138
|
continue;
|
|
137
139
|
// Bake every other clause into the preagg table. Sorted by source ID,
|
|
138
140
|
// so the same filters always give the same SQL, and so the same table.
|
|
139
|
-
const bakedClauses = selection.bakedClauses(
|
|
140
|
-
const staticFilters = selection.bakedPredicates(
|
|
141
|
+
const bakedClauses = selection.bakedClauses(route, activeSourceId);
|
|
142
|
+
const staticFilters = selection.bakedPredicates(route, activeSourceId);
|
|
141
143
|
// Hash-based naming: identical preagg SQL reuses the same table (like Mosaic).
|
|
142
144
|
// CREATE TABLE IF NOT EXISTS avoids rebuilding when switching charts.
|
|
143
145
|
const preaggSQL = preaggSelectSQL(plan.creation, staticFilters);
|
|
@@ -194,7 +196,7 @@ export class Preagg {
|
|
|
194
196
|
const preagg = this.#usable(markId, reg);
|
|
195
197
|
if (!preagg)
|
|
196
198
|
return null;
|
|
197
|
-
const sql = preaggQuerySQL(
|
|
199
|
+
const sql = preaggQuerySQL(reg.descriptor.route ?? NO_ROUTE, reg.descriptor, reg.filterGroup.selection, preagg, reg.handle.params, this.#host.identity);
|
|
198
200
|
return sql ? { sql, table: preagg.table } : null;
|
|
199
201
|
}
|
|
200
202
|
/**
|
|
@@ -308,7 +310,7 @@ export class Preagg {
|
|
|
308
310
|
this.#marks.get(markId)?.building.get(sourceId) === build &&
|
|
309
311
|
reg.descriptor.parts === build.basis.parts &&
|
|
310
312
|
this.#binsFitCurrentView(build) &&
|
|
311
|
-
bakedClausesHold(
|
|
313
|
+
bakedClausesHold(reg.descriptor.route ?? NO_ROUTE, reg.filterGroup.selection, build));
|
|
312
314
|
}
|
|
313
315
|
/**
|
|
314
316
|
* The size guard: whether a planned table would hold more than a quarter of
|
|
@@ -371,7 +373,7 @@ export class Preagg {
|
|
|
371
373
|
for (const [sourceId, state] of own.tables) {
|
|
372
374
|
if (state.basis.parts === reg.descriptor.parts &&
|
|
373
375
|
this.#binsFineEnough(state) &&
|
|
374
|
-
bakedClausesHold(
|
|
376
|
+
bakedClausesHold(reg.descriptor.route ?? NO_ROUTE, selection, state)) {
|
|
375
377
|
// A Map keeps the first insertion position of a key, so listing
|
|
376
378
|
// this entry first and spreading the rest moves it to the front.
|
|
377
379
|
if (!first)
|
|
@@ -16,7 +16,7 @@ import { type BrushActivation } from '../sufficient-statistics';
|
|
|
16
16
|
import { type QueryParts } from '../../components/marks/lifecycle/query-builder';
|
|
17
17
|
import type { MarkHandle, QueryParams } from '../MarkHandle.svelte';
|
|
18
18
|
import type { CategoricalIdentity } from '../categorical-identity';
|
|
19
|
-
import type { Clause, CrossfilterSelection, FilterExpr } from '../CrossfilterSelection.svelte';
|
|
19
|
+
import type { Clause, CrossfilterSelection, FilterExpr, MarkRoute } from '../CrossfilterSelection.svelte';
|
|
20
20
|
import type { QueryDescriptor } from '../QueryEngine';
|
|
21
21
|
/** What planning needs from the categorical identity. */
|
|
22
22
|
export type PreaggIdentity = Pick<CategoricalIdentity, 'idColumn' | 'fieldOfIdColumn' | 'values' | 'dimCovers' | 'viewHas' | 'sqlFor' | 'dimTable' | 'identity'>;
|
|
@@ -35,8 +35,12 @@ export type ActiveDim = ({
|
|
|
35
35
|
/** What activate() needs to know about a source, as FilterGroup sees it at hover time. */
|
|
36
36
|
export interface ActivationSource {
|
|
37
37
|
sourceId: string;
|
|
38
|
-
/**
|
|
39
|
-
|
|
38
|
+
/**
|
|
39
|
+
* The source's categorical field (identity key), or null for a brush or a
|
|
40
|
+
* tuple. With the registrations, decides which marks the source would
|
|
41
|
+
* filter: a mark that reads this field dims by it and needs no table.
|
|
42
|
+
*/
|
|
43
|
+
categoricalKey: string | null;
|
|
40
44
|
dims: ActiveDim[];
|
|
41
45
|
/**
|
|
42
46
|
* For a source with bin dims: writes its current view, `[d0, d1, pixel
|
|
@@ -66,6 +70,11 @@ export interface PreaggCreation {
|
|
|
66
70
|
select: Record<string, ExprNode>;
|
|
67
71
|
groupby: ExprNode[];
|
|
68
72
|
orderby: ExprNode[];
|
|
73
|
+
/**
|
|
74
|
+
* The mark's own row filters (`filter()`), applied to the source rows inside the
|
|
75
|
+
* build: the table groups them away, so the read could not apply them.
|
|
76
|
+
*/
|
|
77
|
+
where: ExprNode[];
|
|
69
78
|
/**
|
|
70
79
|
* Fields grouped by value inside and mapped to their ids on the grouped
|
|
71
80
|
* result: `raw` is the inner column, `alias` the id column, `dim` the dim
|
|
@@ -172,14 +181,14 @@ export declare function estimateGroups(markKeys: GroupKey[], activeKeys: GroupKe
|
|
|
172
181
|
* mismatch the table's SQL is rebuilt from the current clauses and compared.
|
|
173
182
|
* Once that fails, frames that still see the same clauses skip the rebuild.
|
|
174
183
|
*/
|
|
175
|
-
export declare function bakedClausesHold(
|
|
184
|
+
export declare function bakedClausesHold(route: MarkRoute, selection: CrossfilterSelection, state: PreaggState): boolean;
|
|
176
185
|
/**
|
|
177
186
|
* The query a mark runs on a preagg table, or null when the table cannot
|
|
178
187
|
* answer it (a brush end past its window, a toggled value with no id).
|
|
179
188
|
* Builds modified QueryParts from the stored outputSelect + bin filters,
|
|
180
189
|
* then delegates to assembleQuery().
|
|
181
190
|
*/
|
|
182
|
-
export declare function preaggQuerySQL(
|
|
191
|
+
export declare function preaggQuerySQL(route: MarkRoute, descriptor: QueryDescriptor, selection: CrossfilterSelection, preagg: PreaggState, params: QueryParams, identity: PreaggIdentity): string | null;
|
|
183
192
|
/** A preagg table's SELECT. Build and validity check share it, so equal SQL means an equal table. */
|
|
184
193
|
export declare function preaggSelectSQL(creation: PreaggCreation, filters: FilterExpr[]): string;
|
|
185
194
|
/**
|