Merge pull request #18921 from PeteLevineA/continued-mysql-support

patch: Add Continued MySQL Support
This commit is contained in:
Fredrik Adelöw
2023-08-22 13:23:03 +02:00
committed by GitHub
28 changed files with 172 additions and 66 deletions
@@ -20,11 +20,20 @@
* @param {import('knex').Knex} knex
*/
exports.up = async function up(knex) {
return knex.schema.createTable('static_assets_cache', table => {
const isMySQL = knex.client.config.client.includes('mysql');
await knex.schema.createTable('static_assets_cache', table => {
table.comment(
'A cache of static assets that where previously deployed and may still be lazy-loaded by clients',
);
table.text('path').primary().notNullable().comment('The path of the file');
if (!isMySQL) {
table
.text('path')
.primary()
.notNullable()
.comment('The path of the file');
} else {
table.text('path').notNullable().comment('The path of the file');
}
table
.dateTime('last_modified_at')
.defaultTo(knex.fn.now())
@@ -35,6 +44,12 @@ exports.up = async function up(knex) {
table.binary('content').notNullable().comment('The asset content');
table.index('last_modified_at', 'static_asset_cache_last_modified_at_idx');
});
// specifically for mysql specify a unique index up to 254 characters(mysql limit)
if (isMySQL) {
await knex.schema.raw(
'create unique index static_assets_cache_path_idx on static_assets_cache(path(254));',
);
}
};
/**
@@ -37,7 +37,7 @@ jest.setTimeout(60_000);
describe('StaticAssetsStore', () => {
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
});
it.each(databases.eachSupportedId())(
@@ -153,14 +153,18 @@ describe('StaticAssetsStore', () => {
content: async () => Buffer.alloc(0),
},
]);
// interval check for postgresql
let hourPast = `now() + interval '-3600 seconds'`;
if (knex.client.config.client.includes('mysql')) {
hourPast = `date_sub(now(), interval 3600 second)`;
} else if (knex.client.config.client.includes('sqlite3')) {
hourPast = `datetime('now', '-3600 seconds')`;
}
// Rewrite modified time of "old" to be 1h in the past
const updated = await knex('static_assets_cache')
.where({ path: 'old' })
.update({
last_modified_at: knex.client.config.client.includes('sqlite3')
? knex.raw(`datetime('now', '-3600 seconds')`)
: knex.raw(`now() + interval '-3600 seconds'`),
last_modified_at: knex.raw(hourPast),
});
expect(updated).toBe(1);
@@ -138,14 +138,20 @@ export class StaticAssetsStore implements StaticAssetProvider {
*/
async trimAssets(options: { maxAgeSeconds: number }) {
const { maxAgeSeconds } = options;
let lastModifiedInterval = this.#db.raw(
`now() + interval '${-maxAgeSeconds} seconds'`,
);
if (this.#db.client.config.client.includes('mysql')) {
lastModifiedInterval = this.#db.raw(
`date_sub(now(), interval ${maxAgeSeconds} second)`,
);
} else if (this.#db.client.config.client.includes('sqlite3')) {
lastModifiedInterval = this.#db.raw(`datetime('now', ?)`, [
`-${maxAgeSeconds} seconds`,
]);
}
await this.#db<StaticAssetRow>('static_assets_cache')
.where(
'last_modified_at',
'<=',
this.#db.client.config.client.includes('sqlite3')
? this.#db.raw(`datetime('now', ?)`, [`-${maxAgeSeconds} seconds`])
: this.#db.raw(`now() + interval '${-maxAgeSeconds} seconds'`),
)
.where('last_modified_at', '<=', lastModifiedInterval)
.delete();
}
}
@@ -23,7 +23,7 @@ exports.up = async function up(knex) {
await knex.schema.createTable('metadata', table => {
table.comment('The table of Bazaar metadata');
table
.text('entity_ref')
.string('entity_ref')
.notNullable()
.unique()
.comment('The ref of the entity');
@@ -49,7 +49,7 @@ exports.up = async function up(knex) {
await knex.schema.createTable('members', table => {
table.comment('The table of Bazaar members');
table
.text('entity_ref')
.string('entity_ref')
.notNullable()
.references('metadata.entity_ref')
.onDelete('CASCADE')
@@ -89,9 +89,11 @@ exports.up = async function up(knex) {
await knex.schema.alterTable('members', table => {
table
.integer('item_id')
.unsigned()
.references('metadata.id')
.onDelete('CASCADE')
.comment('Id of the associated item');
table.dropForeign('entity_ref');
table.dropColumn('entity_ref');
});
}
@@ -36,7 +36,7 @@ jest.setTimeout(60_000);
describe('DatabaseHandler', () => {
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'],
});
function createDatabaseManager(
@@ -24,7 +24,7 @@ import { WrapperProviders } from './WrapperProviders';
describe('WrapperProviders', () => {
const applyDatabaseMigrations = jest.fn();
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'],
});
const config = new ConfigReader({});
const logger = getVoidLogger();
@@ -37,7 +37,7 @@ exports.up = async function up(knex) {
.notNullable()
.comment('The unprocessed entity (in original form) as JSON');
table
.text('processed_entity')
.text('processed_entity', 'longtext')
.nullable()
.comment('The processed entity (not yet stitched) as JSON');
table
@@ -83,7 +83,7 @@ exports.up = async function up(knex) {
.notNullable()
.comment('Random value representing a unique stitch attempt ticket');
table
.text('final_entity')
.text('final_entity', 'longtext')
.nullable()
.comment('The JSON encoded final entity');
table.index('entity_id', 'final_entities_entity_id_idx');
@@ -138,6 +138,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
type,
}),
);
await tx.batchInsert(
'relations',
this.deduplicateRelations(relationRows),
@@ -34,7 +34,7 @@ exports.up = async function up(knex) {
.comment('An insert counter to ensure ordering');
table.uuid('id').notNullable().comment('The ID of the code coverage');
table
.text('entity')
.string('entity')
.notNullable()
.comment('The entity ref that this code coverage applies to');
table
@@ -32,7 +32,7 @@ exports.up = async function up(knex) {
.comment('An insert counter to ensure ordering');
table.uuid('id').notNullable().comment('The ID of the Linguist result');
table
.text('entity_ref')
.string('entity_ref')
.unique()
.notNullable()
.comment('The entity ref that this Linguist result applies to');
@@ -261,18 +261,21 @@ export class DatabaseTaskStore implements TaskStore {
tasks: { taskId: string }[];
}> {
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 rawRows = await this.db<RawDbTaskRow>('tasks')
.where('status', 'processing')
.andWhere(
'last_heartbeat_at',
'<=',
this.db.client.config.client.includes('sqlite3')
? this.db.raw(`datetime('now', ?)`, [`-${timeoutS} seconds`])
: this.db.raw(`? - interval '${timeoutS} seconds'`, [
this.db.fn.now(),
]),
);
.andWhere('last_heartbeat_at', '<=', heartbeatInterval);
const tasks = rawRows.map(row => ({
taskId: row.id,
}));
@@ -25,7 +25,7 @@ exports.up = async function up(knex) {
'The table for tech insight fact schemas. Containing a versioned data model definition for a collection of facts.',
);
table
.text('id')
.string('id')
.notNullable()
.comment('Identifier of the fact retriever plugin/package');
table
@@ -25,7 +25,7 @@ exports.up = async function up(knex) {
'The table for tech insight fact collections. Contains facts for individual fact retriever namespace/ref.',
);
table
.text('id')
.string('id')
.notNullable()
.comment('Unique identifier of the fact retriever plugin/package');
table
@@ -40,7 +40,7 @@ exports.up = async function up(knex) {
.notNullable()
.comment('The timestamp when this entry was created');
table
.text('entity')
.string('entity')
.notNullable()
.comment('Identifier of the entity these facts relate to');
table