From c58f8fd1477b6ca536d54ce6ef3aae2b92cd944a Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 26 Apr 2021 15:38:31 +0200 Subject: [PATCH] Add all migration files to migrationsv2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Ben Lambert Co-authored-by: Patrik Oldsberg Signed-off-by: Johan Haals --- .../migrationsv2/20200511113813_init.js | 133 ++++++++++ ...0200520140700_location_update_log_table.js | 43 ++++ ...7114117_location_update_log_latest_view.js | 45 ++++ .../migrationsv2/20200702153613_entities.js | 236 ++++++++++++++++++ ..._location_update_log_latest_deduplicate.js | 44 ++++ ...904_location_update_log_duplication_fix.js | 87 +++++++ .../20200807120600_entitySearch.js | 41 +++ .../20200809202832_add_bootstrap_location.js | 42 ++++ .../20200923104503_case_insensitivity.js | 32 +++ .../20201005122705_add_entity_full_name.js | 60 +++++ .../20201006130744_entity_data_column.js | 66 +++++ ...6203131_entity_remove_redundant_columns.js | 50 ++++ .../20201007201501_index_entity_search.js | 37 +++ .../20201019130742_add_relations_table.js | 54 ++++ .../20201123205611_relations_table_uniq.js | 93 +++++++ .../migrationsv2/20201210185851_fk_index.js | 45 ++++ .../20201230103504_update_log_varchar.js | 73 ++++++ .../20210209121210_locations_fk_index.js | 45 ++++ .../src/next/database/DatabaseManager.ts | 15 -- 19 files changed, 1226 insertions(+), 15 deletions(-) create mode 100644 plugins/catalog-backend/migrationsv2/20200511113813_init.js create mode 100644 plugins/catalog-backend/migrationsv2/20200520140700_location_update_log_table.js create mode 100644 plugins/catalog-backend/migrationsv2/20200527114117_location_update_log_latest_view.js create mode 100644 plugins/catalog-backend/migrationsv2/20200702153613_entities.js create mode 100644 plugins/catalog-backend/migrationsv2/20200721115244_location_update_log_latest_deduplicate.js create mode 100644 plugins/catalog-backend/migrationsv2/20200805163904_location_update_log_duplication_fix.js create mode 100644 plugins/catalog-backend/migrationsv2/20200807120600_entitySearch.js create mode 100644 plugins/catalog-backend/migrationsv2/20200809202832_add_bootstrap_location.js create mode 100644 plugins/catalog-backend/migrationsv2/20200923104503_case_insensitivity.js create mode 100644 plugins/catalog-backend/migrationsv2/20201005122705_add_entity_full_name.js create mode 100644 plugins/catalog-backend/migrationsv2/20201006130744_entity_data_column.js create mode 100644 plugins/catalog-backend/migrationsv2/20201006203131_entity_remove_redundant_columns.js create mode 100644 plugins/catalog-backend/migrationsv2/20201007201501_index_entity_search.js create mode 100644 plugins/catalog-backend/migrationsv2/20201019130742_add_relations_table.js create mode 100644 plugins/catalog-backend/migrationsv2/20201123205611_relations_table_uniq.js create mode 100644 plugins/catalog-backend/migrationsv2/20201210185851_fk_index.js create mode 100644 plugins/catalog-backend/migrationsv2/20201230103504_update_log_varchar.js create mode 100644 plugins/catalog-backend/migrationsv2/20210209121210_locations_fk_index.js diff --git a/plugins/catalog-backend/migrationsv2/20200511113813_init.js b/plugins/catalog-backend/migrationsv2/20200511113813_init.js new file mode 100644 index 0000000000..7f3d75e35c --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20200511113813_init.js @@ -0,0 +1,133 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + return ( + knex.schema + // + // locations + // + .createTable('locations', table => { + table.comment( + 'Registered locations that shall be contiuously scanned for catalog item updates', + ); + table + .uuid('id') + .primary() + .notNullable() + .comment('Auto-generated ID of the location'); + table.string('type').notNullable().comment('The type of location'); + table + .string('target') + .notNullable() + .comment('The actual target of the location'); + }) + // + // entities + // + .createTable('entities', table => { + table.comment('All entities currently stored in the catalog'); + table.uuid('id').primary().comment('Auto-generated ID of the entity'); + table + .uuid('location_id') + .references('id') + .inTable('locations') + .nullable() + .comment('The location that originated the entity'); + table + .string('etag') + .notNullable() + .comment( + 'An opaque string that changes for each update operation to any part of the entity, including metadata.', + ); + table + .string('generation') + .notNullable() + .unsigned() + .comment( + 'A positive nonzero number that indicates the current generation of data for this entity; the value is incremented each time the spec changes.', + ); + table + .string('api_version') + .notNullable() + .comment('The apiVersion field of the entity'); + table + .string('kind') + .notNullable() + .comment('The kind field of the entity'); + table + .string('name') + .nullable() + .comment('The metadata.name field of the entity'); + table + .string('namespace') + .nullable() + .comment('The metadata.namespace field of the entity'); + table + .string('metadata') + .notNullable() + .comment('The entire metadata JSON blob of the entity'); + table + .string('spec') + .nullable() + .comment('The entire spec JSON blob of the entity'); + }) + .alterTable('entities', table => { + // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta + table.unique(['kind', 'name', 'namespace'], 'entities_unique_name'); + }) + // + // entities_search + // + .createTable('entities_search', table => { + table.comment( + 'Flattened key-values from the entities, used for quick filtering', + ); + table + .uuid('entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .comment('The entity that matches this key/value'); + table + .string('key') + .notNullable() + .comment('A key that occurs in the entity'); + table + .string('value') + .nullable() + .comment('The corresponding value to match on'); + }) + ); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + return knex.schema + .dropTable('entities_search') + .alterTable('entities', table => { + table.dropUnique([], 'entities_unique_name'); + }) + .dropTable('entities') + .dropTable('locations'); +}; diff --git a/plugins/catalog-backend/migrationsv2/20200520140700_location_update_log_table.js b/plugins/catalog-backend/migrationsv2/20200520140700_location_update_log_table.js new file mode 100644 index 0000000000..d8093fc9b4 --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20200520140700_location_update_log_table.js @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + return knex.schema.createTable('location_update_log', table => { + table.uuid('id').primary(); + table.enum('status', ['success', 'fail']).notNullable(); + table.dateTime('created_at').defaultTo(knex.fn.now()).notNullable(); + table.string('message'); + table + .uuid('location_id') + .references('id') + .inTable('locations') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table.string('entity_name').nullable(); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + return knex.schema.dropTableIfExists('location_update_log'); +}; diff --git a/plugins/catalog-backend/migrationsv2/20200527114117_location_update_log_latest_view.js b/plugins/catalog-backend/migrationsv2/20200527114117_location_update_log_latest_view.js new file mode 100644 index 0000000000..a0f0f33a65 --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20200527114117_location_update_log_latest_view.js @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + // Get list sorted by created_at timestamp in descending order + // Grouped by location_id + return knex.schema.raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(created_at) AS MAXDATE + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.created_at = t2.MAXDATE + ORDER BY created_at DESC; + `); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + return knex.schema.raw(`DROP VIEW location_update_log_latest;`); +}; diff --git a/plugins/catalog-backend/migrationsv2/20200702153613_entities.js b/plugins/catalog-backend/migrationsv2/20200702153613_entities.js new file mode 100644 index 0000000000..0f1c204f9b --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20200702153613_entities.js @@ -0,0 +1,236 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + // SQLite does not support FK and PK + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities_search', table => { + table.dropForeign(['entity_id']); + }); + await knex.schema.alterTable('entities', table => { + table.dropPrimary('entities_pkey'); + }); + } + await knex.schema.alterTable('entities', table => { + table.dropUnique([], 'entities_unique_name'); + }); + // Setup temporary tables + await knex.schema.renameTable('entities_search', 'tmp_entities_search'); + await knex.schema.renameTable('entities', 'tmp_entities'); + + // + // entities + // + await knex.schema + .createTable('entities', table => { + table.comment('All entities currently stored in the catalog'); + table.uuid('id').primary().comment('Auto-generated ID of the entity'); + table + .uuid('location_id') + .references('id') + .inTable('locations') + .nullable() + .comment('The location that originated the entity'); + table + .string('etag') + .notNullable() + .comment( + 'An opaque string that changes for each update operation to any part of the entity, including metadata.', + ); + table + .string('generation') + .notNullable() + .unsigned() + .comment( + 'A positive nonzero number that indicates the current generation of data for this entity; the value is incremented each time the spec changes.', + ); + table + .string('api_version') + .notNullable() + .comment('The apiVersion field of the entity'); + table + .string('kind') + .notNullable() + .comment('The kind field of the entity'); + table + .string('name') + .nullable() + .comment('The metadata.name field of the entity'); + table + .string('namespace') + .nullable() + .comment('The metadata.namespace field of the entity'); + table + .text('metadata') + .notNullable() + .comment('The entire metadata JSON blob of the entity'); + table + .text('spec') + .nullable() + .comment('The entire spec JSON blob of the entity'); + }) + .alterTable('entities', table => { + // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta + table.unique(['kind', 'name', 'namespace'], 'entities_unique_name'); + }); + + await knex.schema.raw(`INSERT INTO entities SELECT * FROM tmp_entities`); + + // + // entities_search + // + await knex.schema.createTable('entities_search', table => { + table.comment( + 'Flattened key-values from the entities, used for quick filtering', + ); + table + .uuid('entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .comment('The entity that matches this key/value'); + table + .string('key') + .notNullable() + .comment('A key that occurs in the entity'); + table + .string('value') + .nullable() + .comment('The corresponding value to match on'); + }); + await knex.schema.raw( + `INSERT INTO entities_search SELECT * FROM tmp_entities_search`, + ); + + // Clean up + await knex.schema.dropTable('tmp_entities'); + return knex.schema.dropTable('tmp_entities_search'); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + // SQLite does not support FK and PK + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities_search', table => { + table.dropForeign(['entity_id']); + }); + await knex.schema.alterTable('entities', table => { + table.dropPrimary('entities_pkey'); + }); + } + await knex.schema.alterTable('entities', table => { + table.dropUnique([], 'entities_unique_name'); + }); + + // Setup temporary tables + await knex.schema.renameTable('entities_search', 'tmp_entities_search'); + await knex.schema.renameTable('entities', 'tmp_entities'); + + // + // entities + // + await knex.schema + .createTable('entities', table => { + table.comment('All entities currently stored in the catalog'); + table.uuid('id').primary().comment('Auto-generated ID of the entity'); + table + .uuid('location_id') + .references('id') + .inTable('locations') + .nullable() + .comment('The location that originated the entity'); + table + .string('etag') + .notNullable() + .comment( + 'An opaque string that changes for each update operation to any part of the entity, including metadata.', + ); + table + .string('generation') + .notNullable() + .unsigned() + .comment( + 'A positive nonzero number that indicates the current generation of data for this entity; the value is incremented each time the spec changes.', + ); + table + .string('api_version') + .notNullable() + .comment('The apiVersion field of the entity'); + table + .string('kind') + .notNullable() + .comment('The kind field of the entity'); + table + .string('name') + .nullable() + .comment('The metadata.name field of the entity'); + table + .string('namespace') + .nullable() + .comment('The metadata.namespace field of the entity'); + table + .string('metadata') + .notNullable() + .comment('The entire metadata JSON blob of the entity'); + table + .string('spec') + .nullable() + .comment('The entire spec JSON blob of the entity'); + }) + .alterTable('entities', table => { + // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta + table.unique(['kind', 'name', 'namespace'], 'entities_unique_name'); + }); + + await knex.schema.raw(`INSERT INTO entities SELECT * FROM tmp_entities`); + + // + // entities_search + // + await knex.schema.createTable('entities_search', table => { + table.comment( + 'Flattened key-values from the entities, used for quick filtering', + ); + table + .uuid('entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .comment('The entity that matches this key/value'); + table + .string('key') + .notNullable() + .comment('A key that occurs in the entity'); + table + .string('value') + .nullable() + .comment('The corresponding value to match on'); + }); + await knex.schema.raw( + `INSERT INTO entities_search SELECT * FROM tmp_entities_search`, + ); + + // Clean up + await knex.schema.dropTable('tmp_entities'); + return knex.schema.dropTable('tmp_entities_search'); +}; diff --git a/plugins/catalog-backend/migrationsv2/20200721115244_location_update_log_latest_deduplicate.js b/plugins/catalog-backend/migrationsv2/20200721115244_location_update_log_latest_deduplicate.js new file mode 100644 index 0000000000..87b41a80fc --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20200721115244_location_update_log_latest_deduplicate.js @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = function up(knex) { + return knex.schema.raw(`DROP VIEW location_update_log_latest;`).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(created_at) AS MAXDATE + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.created_at = t2.MAXDATE + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; +`); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = function down(knex) { + knex.schema.raw(`DROP VIEW location_update_log_latest;`); +}; diff --git a/plugins/catalog-backend/migrationsv2/20200805163904_location_update_log_duplication_fix.js b/plugins/catalog-backend/migrationsv2/20200805163904_location_update_log_duplication_fix.js new file mode 100644 index 0000000000..de2b194cff --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20200805163904_location_update_log_duplication_fix.js @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = function up(knex) { + return knex.schema + .raw('DROP VIEW location_update_log_latest;') + .dropTable('location_update_log') + .createTable('location_update_log', table => { + table.bigIncrements('id').primary(); // instead of uuid, so we can MAX it + table.enum('status', ['success', 'fail']).notNullable(); + table.dateTime('created_at').defaultTo(knex.fn.now()).notNullable(); + table.string('message'); + table + .uuid('location_id') + .references('id') + .inTable('locations') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table.string('entity_name').nullable(); + }).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(id) AS MAXID + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.id = t2.MAXID + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; + `); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = function down(knex) { + return knex.schema + .raw('DROP VIEW location_update_log_latest;') + .dropTable('location_update_log') + .createTable('location_update_log', table => { + table.uuid('id').primary(); + table.enum('status', ['success', 'fail']).notNullable(); + table.dateTime('created_at').defaultTo(knex.fn.now()).notNullable(); + table.string('message'); + table + .uuid('location_id') + .references('id') + .inTable('locations') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table.string('entity_name').nullable(); + }).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(created_at) AS MAXDATE + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.created_at = t2.MAXDATE + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; + `); +}; diff --git a/plugins/catalog-backend/migrationsv2/20200807120600_entitySearch.js b/plugins/catalog-backend/migrationsv2/20200807120600_entitySearch.js new file mode 100644 index 0000000000..45226e53b4 --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20200807120600_entitySearch.js @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + // Sqlite does not support alter column. + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities_search', table => { + table.text('value').nullable().alter(); + }); + } +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + // Sqlite does not support alter column. + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities_search', table => { + table.string('value').nullable().alter(); + }); + } +}; diff --git a/plugins/catalog-backend/migrationsv2/20200809202832_add_bootstrap_location.js b/plugins/catalog-backend/migrationsv2/20200809202832_add_bootstrap_location.js new file mode 100644 index 0000000000..a90813fe85 --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20200809202832_add_bootstrap_location.js @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + // Adds a single 'bootstrap' location that can be used to trigger work in processors. + // This is primarily here to fulfill foreign key constraints. + await knex('locations').insert({ + id: require('uuid').v4(), + type: 'bootstrap', + target: 'bootstrap', + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex('locations') + .where({ + type: 'bootstrap', + target: 'bootstrap', + }) + .del(); +}; diff --git a/plugins/catalog-backend/migrationsv2/20200923104503_case_insensitivity.js b/plugins/catalog-backend/migrationsv2/20200923104503_case_insensitivity.js new file mode 100644 index 0000000000..ea5ba9e58d --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20200923104503_case_insensitivity.js @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex('entities') + .where({ namespace: null }) + .update({ namespace: 'default' }); + await knex('entities_search').update({ + key: knex.raw('LOWER(key)'), + value: knex.raw('LOWER(value)'), + }); +}; + +exports.down = async function down() {}; diff --git a/plugins/catalog-backend/migrationsv2/20201005122705_add_entity_full_name.js b/plugins/catalog-backend/migrationsv2/20201005122705_add_entity_full_name.js new file mode 100644 index 0000000000..aae1861658 --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20201005122705_add_entity_full_name.js @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('entities', table => { + table.text('full_name').nullable(); + }); + + await knex('entities').update({ + full_name: knex.raw( + "LOWER(kind) || ':' || LOWER(COALESCE(namespace, 'default')) || '/' || LOWER(name)", + ), + }); + + // SQLite does not support alter column + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities', table => { + table.text('full_name').notNullable().alter(); + }); + } + + await knex.schema.alterTable('entities', table => { + // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta + table.unique(['full_name'], 'entities_unique_full_name'); + table.dropUnique([], 'entities_unique_name'); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.alterTable('entities', table => { + // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta + table.dropUnique([], 'entities_unique_full_name'); + table.unique(['kind', 'namespace', 'name'], 'entities_unique_name'); + }); + + await knex.schema.alterTable('entities_search', table => { + table.dropColumn('full_name'); + }); +}; diff --git a/plugins/catalog-backend/migrationsv2/20201006130744_entity_data_column.js b/plugins/catalog-backend/migrationsv2/20201006130744_entity_data_column.js new file mode 100644 index 0000000000..a8964efbf6 --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20201006130744_entity_data_column.js @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('entities', table => { + table + .text('data') + .nullable() + .comment('The entire JSON data blob of the entity'); + }); + + await knex('entities').update({ + // apiVersion and kind should not contain any JSON unsafe chars, and both + // metadata and spec are already valid serialized JSON + data: knex.raw( + `'{"apiVersion":"' || api_version || '","kind":"' || kind || '","metadata":' || metadata || COALESCE(',"spec":' || spec, '') || '}'`, + ), + }); + + await knex.schema.alterTable('entities', table => { + table.dropColumn('metadata'); + table.dropColumn('spec'); + }); + + // SQLite does not support ALTER COLUMN. + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities', table => { + table.text('data').notNullable().alter(); + }); + } +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.alterTable('entities', table => { + table + .text('metadata') + .notNullable() + .comment('The entire metadata JSON blob of the entity'); + table + .text('spec') + .nullable() + .comment('The entire spec JSON blob of the entity'); + table.dropColumn('data'); + }); +}; diff --git a/plugins/catalog-backend/migrationsv2/20201006203131_entity_remove_redundant_columns.js b/plugins/catalog-backend/migrationsv2/20201006203131_entity_remove_redundant_columns.js new file mode 100644 index 0000000000..f40df5f73e --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20201006203131_entity_remove_redundant_columns.js @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('entities', table => { + table.dropColumn('api_version'); + table.dropColumn('kind'); + table.dropColumn('name'); + table.dropColumn('namespace'); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.alterTable('entities', table => { + table + .string('api_version') + .notNullable() + .comment('The apiVersion field of the entity'); + table.string('kind').notNullable().comment('The kind field of the entity'); + table + .string('name') + .nullable() + .comment('The metadata.name field of the entity'); + table + .string('namespace') + .nullable() + .comment('The metadata.namespace field of the entity'); + }); +}; diff --git a/plugins/catalog-backend/migrationsv2/20201007201501_index_entity_search.js b/plugins/catalog-backend/migrationsv2/20201007201501_index_entity_search.js new file mode 100644 index 0000000000..77bf0529eb --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20201007201501_index_entity_search.js @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('entities_search', table => { + table.index(['key'], 'entities_search_key'); + table.index(['value'], 'entities_search_value'); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.alterTable('entities_search', table => { + table.dropIndex('', 'entities_search_key'); + table.dropIndex('', 'entities_search_value'); + }); +}; diff --git a/plugins/catalog-backend/migrationsv2/20201019130742_add_relations_table.js b/plugins/catalog-backend/migrationsv2/20201019130742_add_relations_table.js new file mode 100644 index 0000000000..85e729f814 --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20201019130742_add_relations_table.js @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.createTable('entities_relations', table => { + table.comment('All relations between entities in the catalog'); + table + .uuid('originating_entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .notNullable() + .comment('The entity that provided the relation'); + table + .string('source_full_name') + .notNullable() + .comment('The full name of the source entity of the relation'); + table + .string('type') + .notNullable() + .comment('The type of the relation between the entities'); + table + .string('target_full_name') + .notNullable() + .comment('The full name of the target entity of the relation'); + + table.primary(['source_full_name', 'type', 'target_full_name']); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.dropTable('entities_relations'); +}; diff --git a/plugins/catalog-backend/migrationsv2/20201123205611_relations_table_uniq.js b/plugins/catalog-backend/migrationsv2/20201123205611_relations_table_uniq.js new file mode 100644 index 0000000000..9e8198b5eb --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20201123205611_relations_table_uniq.js @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + if (knex.client.config.client === 'sqlite3') { + // sqlite doesn't support dropPrimary so we recreate it properly instead + await knex.schema.dropTable('entities_relations'); + await knex.schema.createTable('entities_relations', table => { + table.comment('All relations between entities in the catalog'); + table + .uuid('originating_entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .notNullable() + .comment('The entity that provided the relation'); + table + .string('source_full_name') + .notNullable() + .comment('The full name of the source entity of the relation'); + table + .string('type') + .notNullable() + .comment('The type of the relation between the entities'); + table + .string('target_full_name') + .notNullable() + .comment('The full name of the target entity of the relation'); + table.index('source_full_name', 'source_full_name_idx'); + }); + } else { + await knex.schema.alterTable('entities_relations', table => { + table.dropPrimary(); + table.index('source_full_name', 'source_full_name_idx'); + }); + } +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + if (knex.client.config.client === 'sqlite3') { + await knex.schema.dropTable('entities_relations'); + await knex.schema.createTable('entities_relations', table => { + table.comment('All relations between entities in the catalog'); + table + .uuid('originating_entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .notNullable() + .comment('The entity that provided the relation'); + table + .string('source_full_name') + .notNullable() + .comment('The full name of the source entity of the relation'); + table + .string('type') + .notNullable() + .comment('The type of the relation between the entities'); + table + .string('target_full_name') + .notNullable() + .comment('The full name of the target entity of the relation'); + + table.primary(['source_full_name', 'type', 'target_full_name']); + }); + } else { + await knex.schema.alterTable('entities_relations', table => { + table.dropIndex([], 'source_full_name_idx'); + table.primary(['source_full_name', 'type', 'target_full_name']); + }); + } +}; diff --git a/plugins/catalog-backend/migrationsv2/20201210185851_fk_index.js b/plugins/catalog-backend/migrationsv2/20201210185851_fk_index.js new file mode 100644 index 0000000000..abb26cd5fc --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20201210185851_fk_index.js @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities_relations', table => { + table.index('originating_entity_id', 'originating_entity_id_idx'); + }); + await knex.schema.alterTable('entities_search', table => { + table.index('entity_id', 'entity_id_idx'); + }); + } +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities_relations', table => { + table.dropIndex([], 'originating_entity_id_idx'); + }); + await knex.schema.alterTable('entities_relations', table => { + table.dropIndex([], 'entity_id_idx'); + }); + } +}; diff --git a/plugins/catalog-backend/migrationsv2/20201230103504_update_log_varchar.js b/plugins/catalog-backend/migrationsv2/20201230103504_update_log_varchar.js new file mode 100644 index 0000000000..d924b0414a --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20201230103504_update_log_varchar.js @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + if (knex.client.config.client !== 'sqlite3') { + // We actually just want to widen columns, but can't do that while a + // view is dependent on them - so we just reconstruct it exactly as it was + await knex.schema + .raw('DROP VIEW location_update_log_latest;') + .alterTable('location_update_log', table => { + table.text('message').alter(); + table.text('entity_name').nullable().alter(); + }).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(id) AS MAXID + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.id = t2.MAXID + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; + `); + } +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + if (knex.client.config.client !== 'sqlite3') { + await knex.schema + .raw('DROP VIEW location_update_log_latest;') + .alterTable('location_update_log', table => { + table.string('message').alter(); + table.string('entity_name').nullable().alter(); + }).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(id) AS MAXID + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.id = t2.MAXID + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; + `); + } +}; diff --git a/plugins/catalog-backend/migrationsv2/20210209121210_locations_fk_index.js b/plugins/catalog-backend/migrationsv2/20210209121210_locations_fk_index.js new file mode 100644 index 0000000000..ccfb1faffb --- /dev/null +++ b/plugins/catalog-backend/migrationsv2/20210209121210_locations_fk_index.js @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities', table => { + table.index('location_id', 'entity_location_id_idx'); + }); + await knex.schema.alterTable('location_update_log', table => { + table.index('location_id', 'update_log_location_id_idx'); + }); + } +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('entities', table => { + table.dropIndex([], 'entity_location_id_idx'); + }); + await knex.schema.alterTable('location_update_log', table => { + table.dropIndex([], 'update_log_location_id_idx'); + }); + } +}; diff --git a/plugins/catalog-backend/src/next/database/DatabaseManager.ts b/plugins/catalog-backend/src/next/database/DatabaseManager.ts index 4c9e45950f..f660f73ecb 100644 --- a/plugins/catalog-backend/src/next/database/DatabaseManager.ts +++ b/plugins/catalog-backend/src/next/database/DatabaseManager.ts @@ -20,7 +20,6 @@ import { v4 as uuidv4 } from 'uuid'; import { Logger } from 'winston'; import { CommonDatabase } from '../../database/CommonDatabase'; import { Database } from '../../database/types'; -import fs from 'fs-extra'; export type CreateDatabaseOptions = { logger: Logger; @@ -35,16 +34,10 @@ export class DatabaseManager { knex: Knex, options: Partial = {}, ): Promise { - const allMigrations = resolvePackagePath( - '@backstage/plugin-catalog-backend', - 'migrations', - ); - const migrationsDir = resolvePackagePath( '@backstage/plugin-catalog-backend', 'migrationsv2', ); - await fs.copy(allMigrations, migrationsDir); await knex.migrate.latest({ directory: migrationsDir, @@ -79,14 +72,6 @@ export class DatabaseManager { public static async createTestDatabaseConnection(): Promise { const config: Knex.Config = { - /* - client: 'pg', - connection: { - host: 'localhost', - user: 'postgres', - password: 'postgres', - }, - */ client: 'sqlite3', connection: ':memory:', useNullAsDefault: true,