Line data Source code
1 : import {
2 : CATALOG_UPDATE_CANCELLED,
3 : CATALOG_UPDATE_FAILED,
4 : CATALOG_UPDATE_REGISTER_QUERY,
5 : CATALOG_UPDATE_PARTIALLY_SUCCEEDED,
6 : CATALOG_UPDATE_SUCCEEDED,
7 : CatalogAction,
8 : ConnectionState,
9 : RELATIONS_TOUCHED,
10 : UPDATE_CATALOG,
11 : } from '../connection_state.js';
12 : import type { RelationName } from './catalog_sql_generator.js';
13 :
14 1 : export enum CatalogUpdateVariant {
15 1 : FULL_CATALOG_REFRESH,
16 : /// Ask the connection about the relations that executed statements touched
17 1 : RELATION_REFRESH,
18 1 : }
19 :
20 1 : export enum CatalogUpdateTaskStatus {
21 1 : STARTED = 0,
22 1 : SUCCEEDED = 1,
23 1 : FAILED = 2,
24 1 : CANCELLED = 3,
25 1 : PARTIALLY_SUCCEEDED = 4,
26 1 : }
27 : export const CATALOG_UPDATE_TASK_STATUS_NAMES: string[] = [
28 : "Started",
29 : "Succeeded",
30 : "Failed",
31 : "Cancelled",
32 : "Partially Succeeded",
33 : ];
34 :
35 : export interface CatalogUpdateTaskState {
36 : /// The task key
37 : taskId: number;
38 : /// The catalog update variant
39 : taskVariant: CatalogUpdateVariant;
40 : /// The status
41 : status: CatalogUpdateTaskStatus;
42 : /// The cancellation signal
43 : cancellation: AbortController;
44 : /// The queries
45 : queries: number[];
46 : /// The loading error (if any)
47 : error: Error | null;
48 : /// The time at which the loading started (if any)
49 : startedAt: Date | null;
50 : /// The time at which the loading finishe (if any)
51 : finishedAt: Date | null;
52 : /// The relations of executed statements that the task answers
53 : touchedRelations?: RelationName[];
54 : }
55 :
56 : /// The attempts to answer the relations of executed statements before the catalog counts as out of date
57 : export const MAX_TOUCHED_ATTEMPTS = 3;
58 :
59 : /// Might the catalog miss what executed statements did, since asking the connection failed?
60 1 : export function isCatalogOutOfDate(connection: ConnectionState): boolean {
61 1 : return connection.catalogUpdates.touchedFailures >= MAX_TOUCHED_ATTEMPTS;
62 1 : }
63 :
64 : /// Merge relation names, every relation once
65 1 : export function mergeRelationNames(into: RelationName[], names: RelationName[]): RelationName[] {
66 1 : const out = [...into];
67 1 : for (const name of names) {
68 1 : if (!out.some(r => r.databaseName === name.databaseName && r.schemaName === name.schemaName && r.tableName === name.tableName)) out.push(name);
69 : }
70 1 : return out;
71 1 : }
72 :
73 : /// The relations that nothing has answered about yet, also those of a running update
74 1 : export function pendingRelations(connection: ConnectionState): RelationName[] {
75 1 : let pending = connection.catalogUpdates.touchedRelations;
76 1 : for (const task of connection.catalogUpdates.tasksRunning.values()) {
77 0 : pending = mergeRelationNames(pending, task.touchedRelations ?? []);
78 : }
79 1 : return pending;
80 1 : }
81 :
82 : /// What a task that didn't answer gives back
83 1 : function returnTouched(state: ConnectionState, update: CatalogUpdateTaskState, failed: boolean) {
84 1 : const touched = (update.touchedRelations?.length ?? 0) > 0;
85 1 : return {
86 1 : touchedRelations: mergeRelationNames(state.catalogUpdates.touchedRelations, update.touchedRelations ?? []),
87 1 : touchedFailures: state.catalogUpdates.touchedFailures + (failed && touched ? 1 : 0),
88 1 : };
89 1 : }
90 :
91 1 : export function isCatalogRefreshRunning(connection: ConnectionState | null): boolean {
92 1 : if (connection == null) return false;
93 1 : const refreshId = connection.catalogUpdates.currentFullRefresh;
94 1 : return refreshId != null && connection.catalogUpdates.tasksRunning.has(refreshId);
95 1 : }
96 :
97 1 : export function reduceCatalogAction(state: ConnectionState, action: CatalogAction): ConnectionState {
98 1 : if (action.type === RELATIONS_TOUCHED) {
99 1 : return state;
100 : }
101 1 : if (action.type == UPDATE_CATALOG) {
102 1 : const [updateId, update] = action.value;
103 1 : state.catalogUpdates.tasksRunning.set(updateId, update);
104 1 : return {
105 1 : ...state,
106 1 : catalogUpdates: {
107 1 : ...state.catalogUpdates,
108 1 : currentFullRefresh: update.taskVariant == CatalogUpdateVariant.FULL_CATALOG_REFRESH
109 1 : ? updateId
110 1 : : state.catalogUpdates.currentFullRefresh,
111 : // An update takes the relations it answers. A statement may have touched others meanwhile, and a full
112 : // refresh leaves them all: only the answer about a relation can say that it is gone.
113 1 : touchedRelations: state.catalogUpdates.touchedRelations.filter(relation => !(update.touchedRelations ?? []).includes(relation)),
114 1 : },
115 1 : };
116 1 : }
117 :
118 1 : const updateId = action.value[0];
119 1 : const update = state.catalogUpdates.tasksRunning.get(updateId);
120 1 : if (!update) {
121 1 : return state;
122 : }
123 1 : if (action.type === CATALOG_UPDATE_REGISTER_QUERY) {
124 1 : state.catalogUpdates.tasksRunning.set(updateId, { ...update, queries: [...update.queries, action.value[1]] });
125 1 : return { ...state, catalogUpdates: { ...state.catalogUpdates } };
126 1 : }
127 :
128 : // The update is done
129 1 : const statusOf = {
130 1 : [CATALOG_UPDATE_CANCELLED]: CatalogUpdateTaskStatus.CANCELLED,
131 1 : [CATALOG_UPDATE_FAILED]: CatalogUpdateTaskStatus.FAILED,
132 1 : [CATALOG_UPDATE_SUCCEEDED]: CatalogUpdateTaskStatus.SUCCEEDED,
133 1 : [CATALOG_UPDATE_PARTIALLY_SUCCEEDED]: CatalogUpdateTaskStatus.PARTIALLY_SUCCEEDED,
134 1 : };
135 1 : const succeeded = action.type === CATALOG_UPDATE_SUCCEEDED;
136 1 : state.catalogUpdates.tasksRunning.delete(updateId);
137 1 : state.catalogUpdates.tasksFinished.set(updateId, {
138 1 : ...update,
139 1 : status: statusOf[action.type],
140 1 : error: succeeded ? null : action.value[1],
141 1 : finishedAt: new Date(),
142 1 : });
143 1 : return {
144 1 : ...state,
145 1 : catalogUpdates: {
146 1 : ...state.catalogUpdates,
147 1 : described: state.catalogUpdates.described || (succeeded && update.taskVariant === CatalogUpdateVariant.FULL_CATALOG_REFRESH),
148 : // A cancelled update was superseded or its connection stopped, that is no failure of the connection. A database
149 : // that failed in a partial update didn't answer about its relations.
150 1 : ...(succeeded ? { touchedFailures: 0 } : returnTouched(state, update, action.type !== CATALOG_UPDATE_CANCELLED)),
151 1 : },
152 1 : };
153 1 : }
|