in gui/frontend/src/data-models/ConnectionDataModel.ts [1743:2285]
id: uuid(),
state: createDataModelEntryState(true, true),
caption: func.name,
schema,
connection: group.parent.parent,
language: func.language,
};
newFunctionEntries.push(functionEntry as ICdmRoutineEntry);
}
}
group.members = newFunctionEntries;
this.notifySubscribers(actions);
} catch (reason) {
const message = convertErrorToString(reason);
void ui.showErrorMessage(`Cannot load functions for schema ${group.parent.caption}: ${message}`, {});
return false;
}
return true;
}
private async updateView(viewEntry: DeepMutable<ICdmViewEntry>): Promise<boolean> {
viewEntry.state.initialized = true;
const actions: Array<{ action: SubscriberAction, entry?: ConnectionDataModelEntry; }> = [];
try {
// Unlike tables, there are no column group nodes. Instead, columns are attached directly to a view node.
const columnNames = await viewEntry.connection.backend.getTableObjectNames(viewEntry.schema,
viewEntry.caption, "Column");
// Remove entries no longer in the column list.
const removedColumns = viewEntry.columns.filter((e) => {
return !columnNames.includes(e.caption);
});
for (const column of removedColumns) {
actions.push({ action: "remove", entry: column as ConnectionDataModelEntry });
}
// Create a new column entries list from the column names in their order. Take over existing
// column entries.
const newColumnEntries: ICdmColumnEntry[] = [];
for (const column of columnNames) {
const existing = viewEntry.columns.find((e) => { return e.caption === column; });
if (existing) {
newColumnEntries.push(existing as ICdmColumnEntry);
continue;
}
const columnEntry: ICdmColumnEntry = {
parent: viewEntry as ICdmViewEntry,
id: uuid(),
type: CdmEntityType.Column,
state: createDataModelEntryState(true),
caption: column,
schema: viewEntry.schema,
table: viewEntry.caption,
connection: viewEntry.parent.parent.parent as ICdmConnectionEntry,
inPK: false,
default: "",
nullable: true,
autoIncrement: false,
};
newColumnEntries.push(columnEntry);
}
viewEntry.columns = newColumnEntries;
this.notifySubscribers(actions);
} catch (reason) {
const message = convertErrorToString(reason);
void ui.showErrorMessage(`Cannot load columns for view ${viewEntry.caption}: ${message}`, {});
return false;
}
return Promise.resolve(true);
}
private async updateColumnsTableGroup(
columnGroup: DeepMutable<ICdmTableGroupEntry<CdmEntityType.Column>>): Promise<boolean> {
columnGroup.state.initialized = true;
const actions: Array<{ action: SubscriberAction, entry?: ConnectionDataModelEntry; }> = [];
try {
const schema = columnGroup.parent.schema;
const table = columnGroup.parent.caption;
const columnNames = await columnGroup.connection.backend.getTableObjectNames(schema, table, "Column");
// Remove entries no longer in the column list.
const removedColumns = columnGroup.members.filter((e) => {
return !columnNames.includes(e.caption);
});
for (const column of removedColumns) {
actions.push({ action: "remove", entry: column as ConnectionDataModelEntry });
}
// Create a new column entries list from the column names in their order. Take over existing
// column entries.
const newColumnEntries: ICdmColumnEntry[] = [];
for (const column of columnNames) {
const info = await columnGroup.connection.backend.getTableObject(schema, table, "Column", column);
const existing = columnGroup.members.find((e) => { return e.caption === column; });
if (existing) {
existing.inPK = info.isPk === 1;
existing.default = info.default;
existing.nullable = info.notNull === 0;
existing.autoIncrement = info.autoIncrement === 1;
newColumnEntries.push(existing as ICdmColumnEntry);
continue;
}
const columnEntry: DeepMutable<ICdmColumnEntry> = {
parent: columnGroup,
id: uuid(),
type: CdmEntityType.Column,
state: createDataModelEntryState(true, true),
caption: column,
schema,
table,
inPK: info.isPk === 1,
default: info.default,
nullable: info.notNull === 0,
autoIncrement: info.autoIncrement === 1,
connection: columnGroup.connection,
};
newColumnEntries.push(columnEntry as ICdmColumnEntry);
}
columnGroup.members = newColumnEntries;
this.notifySubscribers(actions);
} catch (reason) {
const message = convertErrorToString(reason);
void ui.showErrorMessage(`Cannot load columns for table ${columnGroup.parent.caption}: ${message}`, {});
return false;
}
return true;
}
private async updateIndexesTableGroup(
indexGroup: DeepMutable<ICdmTableGroupEntry<CdmEntityType.Index>>): Promise<boolean> {
indexGroup.state.initialized = true;
const actions: Array<{ action: SubscriberAction, entry?: ConnectionDataModelEntry; }> = [];
try {
const schema = indexGroup.parent.schema;
const table = indexGroup.parent.caption;
const indexNames = await indexGroup.connection.backend.getTableObjectNames(schema, table, "Index");
// Remove entries no longer in the index list.
const removedIndexes = indexGroup.members.filter((e) => {
return !indexNames.includes(e.caption);
});
for (const index of removedIndexes) {
actions.push({ action: "remove", entry: index as ConnectionDataModelEntry });
}
// Create a new index entries list from the index names in their order. Take over existing
// index entries.
const newIndexEntries: ICdmIndexEntry[] = [];
for (const index of indexNames) {
const existing = indexGroup.members.find((e) => { return e.caption === index; });
if (existing) {
newIndexEntries.push(existing as ICdmIndexEntry);
continue;
}
const indexEntry: DeepMutable<ICdmIndexEntry> = {
parent: indexGroup,
id: uuid(),
type: CdmEntityType.Index,
state: createDataModelEntryState(true, true),
caption: index,
schema,
table,
connection: indexGroup.connection,
};
newIndexEntries.push(indexEntry as ICdmIndexEntry);
}
indexGroup.members = newIndexEntries;
this.notifySubscribers(actions);
} catch (reason) {
const message = convertErrorToString(reason);
void ui.showErrorMessage(`Cannot load indexes for table ${indexGroup.parent.caption}: ${message}`, {});
return false;
}
return true;
}
private async updateForeignKeysTableGroup(
foreignKeyGroup: DeepMutable<ICdmTableGroupEntry<CdmEntityType.ForeignKey>>): Promise<boolean> {
foreignKeyGroup.state.initialized = true;
const actions: Array<{ action: SubscriberAction, entry?: ConnectionDataModelEntry; }> = [];
try {
const schema = foreignKeyGroup.parent.schema;
const table = foreignKeyGroup.parent.caption;
const foreignKeyNames = await foreignKeyGroup.connection.backend.getTableObjectNames(schema, table,
"Foreign Key");
// Remove entries no longer in the foreign key list.
const removedForeignKeys = foreignKeyGroup.members.filter((e) => {
return !foreignKeyNames.includes(e.caption);
});
for (const foreignKey of removedForeignKeys) {
actions.push({ action: "remove", entry: foreignKey as ConnectionDataModelEntry });
}
// Create a new foreign key entries list from the foreign key names in their order. Take over existing
// foreign key entries.
const newForeignKeyEntries: ICdmForeignKeyEntry[] = [];
for (const foreignKey of foreignKeyNames) {
const existing = foreignKeyGroup.members.find((e) => { return e.caption === foreignKey; });
if (existing) {
newForeignKeyEntries.push(existing as ICdmForeignKeyEntry);
continue;
}
const foreignKeyEntry: DeepMutable<ICdmForeignKeyEntry> = {
parent: foreignKeyGroup,
id: uuid(),
type: CdmEntityType.ForeignKey,
state: createDataModelEntryState(true, true),
caption: foreignKey,
schema,
table,
connection: foreignKeyGroup.connection,
};
newForeignKeyEntries.push(foreignKeyEntry as ICdmForeignKeyEntry);
}
foreignKeyGroup.members = newForeignKeyEntries;
this.notifySubscribers(actions);
} catch (reason) {
const message = convertErrorToString(reason);
void ui.showErrorMessage(`Cannot load foreign keys for table ${foreignKeyGroup.parent.caption}: ` +
`${message}`, {});
return false;
}
return true;
}
private async updateTriggersTableGroup(
triggerGroup: DeepMutable<ICdmTableGroupEntry<CdmEntityType.Trigger>>): Promise<boolean> {
triggerGroup.state.initialized = true;
const actions: Array<{ action: SubscriberAction, entry?: ConnectionDataModelEntry; }> = [];
try {
const schema = triggerGroup.parent.schema;
const table = triggerGroup.parent.caption;
const triggerNames = await triggerGroup.connection.backend.getTableObjectNames(schema, table,
"Foreign Key");
// Remove entries no longer in the trigger list.
const removedTriggers = triggerGroup.members.filter((e) => {
return !triggerNames.includes(e.caption);
});
for (const trigger of removedTriggers) {
actions.push({ action: "remove", entry: trigger as ConnectionDataModelEntry });
}
// Create a new trigger entries list from the trigger names in their order. Take over existing
// trigger entries.
const newTriggerEntries: ICdmTriggerEntry[] = [];
for (const foreignKey of triggerNames) {
const existing = triggerGroup.members.find((e) => { return e.caption === foreignKey; });
if (existing) {
newTriggerEntries.push(existing as ICdmTriggerEntry);
continue;
}
const triggerEntry: DeepMutable<ICdmTriggerEntry> = {
parent: triggerGroup,
id: uuid(),
type: CdmEntityType.Trigger,
state: createDataModelEntryState(true, true),
caption: foreignKey,
schema,
table,
connection: triggerGroup.connection,
};
newTriggerEntries.push(triggerEntry as ICdmTriggerEntry);
}
triggerGroup.members = newTriggerEntries;
this.notifySubscribers(actions);
} catch (reason) {
const message = convertErrorToString(reason);
void ui.showErrorMessage(`Cannot load triggers for table ${triggerGroup.parent.caption}: ${message}`, {});
return false;
}
return true;
}
/**
* Updates all child entries of the MRS root (like MRS services and routers).
*
* @param mrsRoot The MRS root element.
* @param callback An optional callback to report progress.
*
* @returns A promise that resolves once the MRS services/routers have been loaded.
*/
private async updateMrsRoot(mrsRoot: DeepMutable<ICdmRestRootEntry>,
callback?: ProgressCallback): Promise<boolean> {
const actions: Array<{ action: SubscriberAction, entry?: ConnectionDataModelEntry; }> = [];
try {
const backend = mrsRoot.parent.backend;
callback?.("Loading MRS services and routers");
const services = await backend.mrs.listServices();
const status = await backend.mrs.status();
mrsRoot.serviceEnabled = status.serviceEnabled;
// Remove all services that are no longer in the new list.
const removedServices = mrsRoot.services.filter((s) => {
return !services.find((service) => {
return service.id === s.details.id;
});
}) as ICdmRestServiceEntry[];
removedServices.forEach((s) => {
actions.push({ action: "remove", entry: s });
});
mrsRoot.services = mrsRoot.services.filter((s) => {
return services.find((service) => {
return service.id === s.details.id;
});
});
// Next insert all new entries and take over existing entries.
const newList: ICdmRestServiceEntry[] = [];
for (const service of services) {
let label = service.urlContextRoot;
if (service.urlHostName) {
label = label + ` (${service.urlHostName})`;
}
// If this service exists already in the current list, update it.
const existing = mrsRoot.services.find((s) => {
return s.details.id === service.id;
});
if (existing) {
// Entries match. Update certain values and move on to the next one.
newList.push(existing as ICdmRestServiceEntry);
existing.details = service;
existing.caption = label;
this.updateServiceFields(existing as ICdmRestServiceEntry);
actions.push({ action: "update", entry: existing as ICdmRestServiceEntry });
continue;
}
// Must be a new entry then.
const serviceEntry = this.createMrsServiceEntry(mrsRoot as ICdmRestRootEntry, service, label);
newList.push(serviceEntry);
actions.push({ action: "add", entry: serviceEntry });
}
mrsRoot.services = newList;
// We do the same here for routers.
const routers = await backend.mrs.listRouters(10);
const removedRouters = mrsRoot.routerGroup.routers.filter((s) => {
return !routers.find((router) => {
return router.id === s.details.id;
});
}) as ICdmRestRouterEntry[];
removedRouters.forEach((s) => {
actions.push({ action: "remove", entry: s });
});
const newRouterList: ICdmRestRouterEntry[] = [];
for (const router of routers) {
const existing = mrsRoot.routerGroup.routers.find((s) => {
return s.details.id === router.id;
});
let description: string;
if (router.options && router.options.developer) {
description = `[${String(router.options.developer)}] ${router.version}`;
} else {
description = router.version;
}
if (existing) {
existing.details = router;
existing.caption = router.address;
existing.description = description;
newRouterList.push(existing as ICdmRestRouterEntry);
actions.push({ action: "update", entry: existing as ICdmRestRouterEntry });
continue;
}
const routerEntry = this.createMrsRouterEntry(mrsRoot.routerGroup as ICdmRestRouterGroupEntry, router,
description);
newRouterList.push(routerEntry as ICdmRestRouterEntry);
actions.push({ action: "add", entry: routerEntry });
}
mrsRoot.routerGroup.routers = newRouterList;
this.notifySubscribers(actions);
} catch (error) {
const message = convertErrorToString(error);
void ui.showErrorMessage(`An error occurred while retrieving MRS content: ${message}`, {});
return false;
}
return true;
}
private async updateMrsAuthAppGroup(authAppGroup: DeepMutable<ICdmRestAuthAppGroupEntry>,
callback?: ProgressCallback): Promise<boolean> {
const actions: Array<{ action: SubscriberAction, entry?: ConnectionDataModelEntry; }> = [];
try {
const backend = authAppGroup.connection.backend;
authAppGroup.authApps.length = 0;
callback?.("Loading MRS auth apps");
const authApps = await backend.mrs.listAuthApps();
for (const authApp of authApps) {
const name = authApp.name ?? "unknown";
const vendor = authApp.authVendor ?? "unknown";
const authAppEntry: Mutable<ICdmRestAuthAppEntry> = {
parent: authAppGroup as ICdmRestAuthAppGroupEntry,
type: CdmEntityType.MrsAuthApp,
id: uuid(),
state: createDataModelEntryState(),
details: authApp,
caption: name,
description: vendor,
users: [],
services: [],
connection: authAppGroup.connection as ICdmConnectionEntry,
};
authAppEntry.refresh = () => {
return this.updateMrsAuthApp(authAppEntry as ICdmRestAuthAppEntry);
};
authAppEntry.getChildren = () => {
return [
...authAppEntry.users,
//...authAppEntry.services // This list is a candidate to be moved to an own group.
];
};
authAppGroup.authApps.push(authAppEntry as ICdmRestAuthAppEntry);
actions.push({ action: "add", entry: authAppEntry });
}
this.notifySubscribers(actions);
} catch (error) {
const message = convertErrorToString(error);
void ui.showErrorMessage(`An error occurred while retrieving MRS content: ${message}`, {});
return false;
}
return true;
}
private async updateMrsService(serviceEntry: DeepMutable<ICdmRestServiceEntry>): Promise<boolean> {
const actions: Array<{ action: SubscriberAction, entry?: ConnectionDataModelEntry; }> = [];
try {
serviceEntry.state.initialized = true;
const backend = serviceEntry.parent.parent.backend;
serviceEntry.details = await backend.mrs.getService(serviceEntry.details.id,
serviceEntry.details.urlContextRoot, serviceEntry.details.urlHostName, null, null);
serviceEntry.caption = serviceEntry.details.urlContextRoot;
this.updateServiceFields(serviceEntry as ICdmRestServiceEntry);
const showPrivateItems = serviceEntry.parent.showPrivateItems;
// Get all MRS schemas.
serviceEntry.schemas.length = 0;
const schemas = await backend.mrs.listSchemas(serviceEntry.details.id);
for (const schema of schemas) {
const schemaEntry: Mutable<ICdmRestSchemaEntry> = {
parent: serviceEntry as ICdmRestServiceEntry,
type: CdmEntityType.MrsSchema,
id: uuid(),
state: createDataModelEntryState(),
details: schema,
caption: `${schema.requestPath} (${schema.name})`,
dbObjects: [],
connection: serviceEntry.parent.parent as ICdmConnectionEntry,
getChildren: () => { return []; },
};
schemaEntry.refresh = () => {
return this.updateMrsSchema(schemaEntry);
};
schemaEntry.getChildren = () => {
return schemaEntry.dbObjects.filter((s) => {