Path: blob/main/components/gitpod-db/src/periodic-deleter.ts
2498 views
/**1* Copyright (c) 2020 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import { inject, injectable } from "inversify";7import { log } from "@gitpod/gitpod-protocol/lib/util/logging";89import { GitpodTableDescriptionProvider, TableDescription } from "./tables";10import { TypeORM } from "./typeorm/typeorm";1112@injectable()13export class PeriodicDbDeleter {14@inject(GitpodTableDescriptionProvider) protected readonly tableProvider: GitpodTableDescriptionProvider;15@inject(TypeORM) protected readonly typeORM: TypeORM;1617async runOnce(): Promise<number> {18const tickID = new Date().toISOString();19log.info("[PeriodicDbDeleter] Starting to collect deleted rows.", {20periodicDeleterTickId: tickID,21});22const sortedTables = this.tableProvider.getSortedTables();23const toBeDeleted: { table: string; deletions: string[] }[] = [];24for (const table of sortedTables) {25const rowsForTableToDelete = await this.collectRowsToBeDeleted(table);26if (rowsForTableToDelete.deletions.length === 0) {27continue;28}29log.info(30`[PeriodicDbDeleter] Identified ${rowsForTableToDelete.deletions.length} entries in ${rowsForTableToDelete.table} to be deleted.`,31{32periodicDeleterTickId: tickID,33},34);35toBeDeleted.push(rowsForTableToDelete);36}37// when collecting the deletions do so in the inverse order as during update (delete dependency targes first)38const pendingDeletions: Promise<void>[] = [];39for (const { deletions } of toBeDeleted.reverse()) {40for (const deletion of deletions) {41const promise: Promise<void> = this.query(deletion).catch((err) =>42log.error(`[PeriodicDbDeleter] sync error`, err, {43periodicDeleterTickId: tickID,44query: deletion,45}),46);47pendingDeletions.push(promise);48}49}50await Promise.all(pendingDeletions);51log.info("[PeriodicDbDeleter] Finished deleting records.", {52periodicDeleterTickId: tickID,53});54return pendingDeletions.length;55}5657protected async collectRowsToBeDeleted(table: TableDescription): Promise<{ table: string; deletions: string[] }> {58try {59await this.query(`SELECT 1 FROM ${table.name} LIMIT 1`);60} catch (err) {61// table does not exist - we're done here62return { table: table.name, deletions: [] };63}6465const deletions: string[] = [];66const result = { table: table.name, deletions };67if (!table.deletionColumn) {68return result;69}7071const { deletionColumn, primaryKeys } = table;72const markedAsDeletedQuery = `SELECT ${primaryKeys.join(", ")} FROM ${73table.name74} WHERE ${deletionColumn} = true LIMIT 100;`;75const rows = await this.query(markedAsDeletedQuery);7677const whereClauseFn = (row: any) => primaryKeys.map((pk) => `${pk}='${row[pk]}'`).join(" AND ");78for (const i in rows) {79const row = rows[i];80const whereClause = whereClauseFn(row);81deletions.push(`DELETE FROM ${table.name} WHERE ${whereClause} AND ${deletionColumn} = true;`);82}8384return result;85}8687protected async query(sql: string): Promise<any> {88const connection = await this.connection;89const result = await connection.query(sql);90return result;91}9293protected get connection() {94return this.typeORM.getConnection();95}96}979899