id: uuid()

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) => {