Skip to content

Commit d0aae3c

Browse files
authored
Fix SQL errors in initialization of tracks that are already filtered out (#130)
The process summary tracks can have dynamically defined tables that they create dropped during the sequence of asynchronous initialization steps in which they create those tables and query them. Implement an abstraction to simplify the management of these tables: - track the created table and existence thereof - easily drop the table on destruction of the track controller - execute queries on the table if and only if it exists, with fall-back options for when it doesn't Apply the DynamicTable mechanism to manage dynamic tables for the process summary tracks and also the counter tracks that previously neglected to drop their tables (thus fixing a latent bug). Signed-off-by: Christian W. Damus <[email protected]>
1 parent f754e9d commit d0aae3c

File tree

3 files changed

+319
-103
lines changed

3 files changed

+319
-103
lines changed

ui/src/controller/track_controller.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import {BigintMath} from '../base/bigint_math';
1616
import {assertExists} from '../base/logging';
1717
import {Engine} from '../common/engine';
18+
import {QueryResult} from '../common/query_result';
1819
import {Registry} from '../common/registry';
1920
import {TraceTime, TrackState} from '../common/state';
2021
import {
@@ -34,6 +35,10 @@ interface TrackConfig {}
3435

3536
type TrackConfigWithNamespace = TrackConfig&{namespace: string};
3637

38+
// Helper type that asserts that two vararg parameters have the same length
39+
type SameLength<T extends unknown[], U extends unknown[]> =
40+
T extends { length: U['length'] } ? T : never;
41+
3742
// TrackController is a base class overridden by track implementations (e.g.,
3843
// sched slices, nestable slices, counters).
3944
export abstract class TrackController<
@@ -105,6 +110,96 @@ export abstract class TrackController<
105110
return `${prefix}_${idSuffix}`;
106111
}
107112

113+
/**
114+
* Create a dynamic table with an automatically assigned name with
115+
* convenient clean-up and handling of attempts to query the table
116+
* after it has been dropped.
117+
*
118+
* @param {string} key to distinguish this table from other tables
119+
* and views dynamically created by the track
120+
* @param {string|Function} ddl a function that generates the
121+
* table DDL using the dynamically assigned name of the
122+
* table and its dependencies from which it extracts data.
123+
* The DDL factory must accept a parameter for each dependency,
124+
* in order, to get its name
125+
* @param {DynamicTable[]} withDependencies optional dynamic tables
126+
* from which the new table extracts data, thus being dependencies
127+
* @return {DynamicTable} the dynamic table manager
128+
*/
129+
protected createDynamicTable
130+
<DN extends string[], DT extends DynamicTable[]>(
131+
key: string,
132+
ddl: (name: string, ...dependencies: DN) => string,
133+
...withDependencies: SameLength<DT, DN>): DynamicTable {
134+
return this.createDynamicTableOrView('table', key, ddl, ...withDependencies);
135+
}
136+
/**
137+
* Create a dynamic view with an automatically assigned name with
138+
* convenient clean-up and handling of attempts to query the view
139+
* after it has been dropped.
140+
*
141+
* @param {string} key to distinguish this view from other tables
142+
* and views dynamically created by the track
143+
* @param {string|Function} ddl a function that generates the
144+
* view DDL using the dynamically assigned name of the
145+
* view and its dependencies from which it extracts data.
146+
* The DDL factory must accept a parameter for each dependency,
147+
* in order, to get its name
148+
* @param {DynamicTable[]} withDependencies optional dynamic tables
149+
* from which the new view extracts data, thus being dependencies
150+
* @return {DynamicTable} the dynamic view manager
151+
*/
152+
protected createDynamicView
153+
<DN extends string[], DT extends DynamicTable[]>(
154+
key: string,
155+
ddl: (name: string, ...dependencies: DN) => string,
156+
...withDependencies: SameLength<DT, DN>): DynamicTable {
157+
return this.createDynamicTableOrView('view', key, ddl, ...withDependencies);
158+
}
159+
private createDynamicTableOrView
160+
<DN extends string[], DT extends DynamicTable[]>(
161+
type: DynamicTable['type'],
162+
key: string,
163+
ddl: (name: string, ...dependencies: DN) => string,
164+
...withDependencies: SameLength<DT, DN>): DynamicTable {
165+
const name = this.tableName(key);
166+
if (withDependencies.some((dep) => !dep.exists)) {
167+
return DynamicTable.NONE;
168+
}
169+
170+
const dependencyNames = withDependencies.map((dep) => dep.name) as DN;
171+
172+
let tableExists = true;
173+
const pendingCreation = this.query(ddl(name, ...dependencyNames));
174+
return {
175+
type,
176+
name,
177+
get exists() {
178+
return tableExists;
179+
},
180+
drop: async () => {
181+
tableExists = false;
182+
await pendingCreation;
183+
await this.query(`drop ${type} if exists ${name}`);
184+
},
185+
query: async <T, E = undefined>(
186+
query: string | ((tableName: string) => string),
187+
resultCase?: (result: QueryResult) => T,
188+
elseCase?: () => E) => {
189+
if (!tableExists) {
190+
return resultCase ? false : elseCase?.call(this);
191+
}
192+
const sql = typeof query === 'function' ? query(name) : query;
193+
await pendingCreation;
194+
const result = await this.query(sql);
195+
if (!resultCase) {
196+
return true;
197+
}
198+
return resultCase.call(this, result);
199+
},
200+
};
201+
}
202+
108203
shouldSummarize(resolution: number): boolean {
109204
// |resolution| is in s/px (to nearest power of 10) assuming a display
110205
// of ~1000px 0.0008 is 0.8s.
@@ -279,6 +374,77 @@ export abstract class TrackController<
279374
}
280375
}
281376

377+
/**
378+
* A wrapper for access to a dynamic-defined table or view
379+
* that may or may not exist at any given time.
380+
*/
381+
export interface DynamicTable {
382+
/** What kind of dynamic object that is encapsulated. */
383+
readonly type: 'table'|'view';
384+
/** The name of the dynamic object that is encapsulated. */
385+
readonly name: string;
386+
/** Whether the dynamic object that is encapsulated currently exists. */
387+
readonly exists: boolean;
388+
/**
389+
* Drop the dynamically-defined table or view.
390+
* From this point on, the query APIs will shunt to the else case.
391+
*/
392+
drop(): Promise<void>;
393+
/**
394+
* Perform an UPDATE, DELETE, or other statement that does not return
395+
* results, if and only if the dynamic table exists.
396+
*/
397+
query(
398+
query: string | ((tableName: string) => string)): Promise<boolean>;
399+
/**
400+
* Perform a SELECT query, if and only if the dynamic table exists, in
401+
* which case the query result is sent to the given call-back function
402+
* to process. There is no else-case call-back.
403+
*
404+
* @param query a query string or factory, which latter accepts the
405+
* dynamically-assigned table or view name as a parameter
406+
* @param resultCase a call-back function to process the query result
407+
*/
408+
query<T>(
409+
query: string | ((tableName: string) => string),
410+
resultCase: (result: QueryResult) => T): Promise<T | undefined>;
411+
/**
412+
* Perform a SELECT query, if and only if the dynamic table exists, in
413+
* which case the query result is sent to the given call-back function
414+
* to process. If the table does not exist, return the result of the
415+
* else-case call-back function, instead.
416+
*
417+
* @param query a query string or factory, which latter accepts the
418+
* dynamically-assigned table or view name as a parameter
419+
* @param resultCase a call-back function to process the query result
420+
* @param elseCase a call-back function to call in the eventuality
421+
that the table has already been dropped
422+
*/
423+
query<T, E>(
424+
query: string | ((tableName: string) => string),
425+
resultCase: (result: QueryResult) => T,
426+
elseCase: () => E): Promise<T | E>;
427+
}
428+
429+
export namespace DynamicTable {
430+
/** Placeholder for a dynamic table that has never (yet) been created. */
431+
export const NONE: DynamicTable = {
432+
type: 'view',
433+
name: '<none>',
434+
exists: false,
435+
drop: () => Promise.resolve(),
436+
query: async <T, E = undefined>(
437+
_query: string | ((tableName: string) => string),
438+
resultCase?: (result: QueryResult) => T,
439+
elseCase?: () => E) => {
440+
if (resultCase === undefined) {
441+
return false;
442+
}
443+
return elseCase?.();
444+
},
445+
};
446+
}
447+
282448
export interface TrackControllerArgs {
283449
trackId: string;
284450
engine: Engine;

0 commit comments

Comments
 (0)