diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 0ce4e38659..35119368fa 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -41,6 +41,7 @@ import { import { DateTime, Duration } from 'luxon'; import { TaskRecovery, TaskSpec } from '@backstage/plugin-scaffolder-common'; import { trimEventsTillLastRecovery } from './taskRecoveryHelper'; +import { intervalFromNowTill } from './dbUtil'; const migrationsDir = resolvePackagePath( '@backstage/plugin-scaffolder-backend', @@ -294,18 +295,7 @@ export class DatabaseTaskStore implements TaskStore { tasks: { taskId: string; recovery?: TaskRecovery }[]; }> { const { timeoutS } = options; - let heartbeatInterval = this.db.raw(`? - interval '${timeoutS} seconds'`, [ - this.db.fn.now(), - ]); - if (this.db.client.config.client.includes('mysql')) { - heartbeatInterval = this.db.raw( - `date_sub(now(), interval ${timeoutS} second)`, - ); - } else if (this.db.client.config.client.includes('sqlite3')) { - heartbeatInterval = this.db.raw(`datetime('now', ?)`, [ - `-${timeoutS} seconds`, - ]); - } + const heartbeatInterval = intervalFromNowTill(timeoutS, this.db); const rawRows = await this.db('tasks') .where('status', 'processing') .andWhere('last_heartbeat_at', '<=', heartbeatInterval); @@ -498,19 +488,7 @@ export class DatabaseTaskStore implements TaskStore { const timeoutS = Duration.fromObject(options.timeout).as('seconds'); await this.db.transaction(async tx => { - let heartbeatInterval = this.db.raw( - `? - interval '${timeoutS} seconds'`, - [this.db.fn.now()], - ); - if (this.db.client.config.client.includes('mysql')) { - heartbeatInterval = this.db.raw( - `date_sub(now(), interval ${timeoutS} second)`, - ); - } else if (this.db.client.config.client.includes('sqlite3')) { - heartbeatInterval = this.db.raw(`datetime('now', ?)`, [ - `-${timeoutS} seconds`, - ]); - } + const heartbeatInterval = intervalFromNowTill(timeoutS, this.db); const result = await tx('tasks') .where('status', 'processing') diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.test.ts new file mode 100644 index 0000000000..9ccc17b8a4 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import knexFactory, { Knex } from 'knex'; +import { intervalFromNowTill } from './dbUtil'; + +class KnexBuilder { + public build(client: string): Knex { + return knexFactory({ client, useNullAsDefault: true }); + } +} + +describe('util', () => { + describe('intervalFromNowTill', () => { + const databases = [ + { client: 'sqlite3', expected: "datetime('now', '-5 seconds')" }, + { client: 'mysql', expected: 'date_sub(now(), interval 5 second)' }, + { client: 'pg', expected: "CURRENT_TIMESTAMP - interval '5 seconds'" }, + ]; + + it.each(databases)('for client $client', ({ client, expected }) => { + const knex = new KnexBuilder().build(client); + const result = intervalFromNowTill(5, knex); + + expect(result.toString()).toBe(expected); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.ts new file mode 100644 index 0000000000..afc4e8c0d6 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Knex } from 'knex'; + +export const intervalFromNowTill = (timeoutS: number, knex: Knex) => { + let heartbeatInterval = knex.raw(`? - interval '${timeoutS} seconds'`, [ + knex.fn.now(), + ]); + if (knex.client.config.client.includes('mysql')) { + heartbeatInterval = knex.raw( + `date_sub(now(), interval ${timeoutS} second)`, + ); + } else if (knex.client.config.client.includes('sqlite3')) { + heartbeatInterval = knex.raw(`datetime('now', ?)`, [ + `-${timeoutS} seconds`, + ]); + } + return heartbeatInterval; +};