Skip to content
This repository has been archived by the owner on Oct 7, 2024. It is now read-only.

feat(migration-scripts) add sql-blueprinting script #107

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions sql-blueprinting/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DATABASE_BLUEPRINT_NAME=clean_db
DATABASE_TARGET_NAME=existing_db

DATABASE_HOST=
DATABASE_PORT=
DATABASE_USER=
DATABASE_PASSWORD=
DATABASE_NAME=
3 changes: 3 additions & 0 deletions sql-blueprinting/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
*.env
package-lock.json
26 changes: 26 additions & 0 deletions sql-blueprinting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# SQL Blueprinting

Compare two databases and drop the tables that are not common in both.
We run our strapi project in a new sql db so it creates its clean structure.
This db can be used as a blueprint since it is created by our strapi current state and doesnt have old entries etc.

## Usage example
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I understand that this should be used with two databases of the same version (eg v3), can you add some clarification to that point as it may confuse some users.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will update the README


- DB1 is a blueprint db that contains only a schema, we will use this db as a structure referance.
- DB2 is a production db that contains the data and a schema.
- We want to drop from the DB2 (prod) the tables that does not appear in the structure of DB1
- After cleaning our prod db according to blueprint we can migrate it to v4

## Description

Since we have to cleanup by order keys, columns and finally the tables, the db sets foreign key checks to 0 and after running back to 1.

## Run

- npm i
- npm run start

## Important Notes

- Please use this script on clone of your production db.
- This script drops all columns, collections and tables that does not exist in blueprint database, so use it carefully.
136 changes: 136 additions & 0 deletions sql-blueprinting/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import chalk from 'chalk';
import { config } from 'dotenv';
import mysql from 'mysql2/promise';

config();

const db1 = process.env.DATABASE_BLUEPRINT_NAME; //reference database
const db2 = process.env.DATABASE_TARGET_NAME; // target database

const connection = await mysql.createConnection({
host: process.env.DATABASE_HOST,
port: process.env.DATABASE_PORT,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
});

connection.connect((err) => {
if (err) throw err;
console.log(chalk.bold.greenBright('Connected to the database!'));
});

const getTables = async (db) => {
const [tables] = await connection.query(
'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?',
[db]
);
return tables;
};

const dropTable = async (db, table) => {
await connection.query(`DROP TABLE IF EXISTS ??.??`, [db, table]);
return `The table ${chalk.bold.redBright(table)} does not exists in both databases. Dropping...`;
};

const getColumns = async (db, table) => {
const [columns] = await connection.query(
'SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?',
[db, table]
);
return columns;
};

const dropColumn = async (db, table, column) => {
await connection.query(`ALTER TABLE ??.?? DROP COLUMN ??`, [db, table, column]);
return `The column ${chalk.bold.redBright(column)} does not exists in both ${chalk.bold.redBright(
table
)} tables. Dropping...`;
};

const dropRow = async (db, table, column, value) => {
await connection.query(`DELETE FROM ??.?? WHERE ?? = ?`, [db, table, column, value]);
return `The row ${chalk.bold.redBright(value)} does not exists in both ${chalk.bold.redBright(
table
)} tables. Dropping...`;
};

const toggleForeignKeyCheck = async (state) => {
await connection.query(`SET FOREIGN_KEY_CHECKS = ${state}`);
return 'Foreign Key Check is set to ' + state + '!';
};

const getCoreStore = async (db) => {
const [coreStore] = await connection.query('SELECT * FROM ??.core_store', [db]);
return coreStore;
};

(async () => {
try {
let foreignKeyCheckState = 0;
toggleForeignKeyCheck(foreignKeyCheckState).then((res) =>
console.log(chalk.bold.yellowBright(res))
);
const tableNames_db1 = await getTables(db1);
const tableNames_db2 = await getTables(db2);

for (const tableName_db2 of tableNames_db2) {
let tableExistanceFlag = false;
let targetTableName = tableName_db2.TABLE_NAME;
tableNames_db1.forEach((table_db1) => {
if (targetTableName === table_db1.TABLE_NAME) {
tableExistanceFlag = true;
}
});
if (tableExistanceFlag && targetTableName !== 'core_store') {
console.log(
`The table ${chalk.bold.greenBright(targetTableName)} exists in both databases.`
);
const columns_db1 = await getColumns(db1, targetTableName);
const columns_db2 = await getColumns(db2, targetTableName);

for (const column_db2 of columns_db2) {
let columnExistanceFlag = false;
let columnNameDB2 = column_db2.COLUMN_NAME;
columns_db1.forEach((column_db1) => {
if (columnNameDB2 === column_db1.COLUMN_NAME) {
columnExistanceFlag = true;
}
});
if (!columnExistanceFlag) {
const dropColumnMsg = await dropColumn(db2, targetTableName, columnNameDB2);
console.log(dropColumnMsg);
}
}
} else if (targetTableName === 'core_store') {
const coreStore1 = await getCoreStore(db1);
const coreStore2 = await getCoreStore(db2);
for (const coreStore2Item of coreStore2) {
let coreStoreExistanceFlag = false;
let coreStore2ItemKey = coreStore2Item.key;
coreStore1.forEach((coreStore1Item) => {
if (coreStore2ItemKey === coreStore1Item.key) {
coreStoreExistanceFlag = true;
}
});
if (!coreStoreExistanceFlag) {
const dropRowMsg = await dropRow(db2, targetTableName, 'key', coreStore2ItemKey);
console.log(dropRowMsg);
}
}
} else {
const dropTableMsg = await dropTable(db2, targetTableName);
console.log(dropTableMsg);
}
}
foreignKeyCheckState = 1;
toggleForeignKeyCheck(foreignKeyCheckState)
.then((res) => console.log(chalk.bold.yellowBright(res)))
.then(() => {
console.log('Database cleanup is done, closing connection...');
connection.end();
});
} catch (err) {
console.log(err);
}
})();
22 changes: 22 additions & 0 deletions sql-blueprinting/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "sql-blueprinting",
"version": "0.1.0",
"description": "",
"main": "index.js",
"author": "FotisVasilopoulos",
"scripts": {
"start": "node index.js"
},
"dependencies": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also need to support PG and SQLite for us to merge it

Copy link
Author

@FotisVasilopoulos FotisVasilopoulos Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will rename it to "mySQL-blueprinting" for this particular reason, but I can rename it to "db-blueprinting" and add more dbs support in the future. Is it blocking if I define that it works only for mySQL atm?

"chalk": "5.2.0",
"mysql": "2.18.1",
"mysql2": "3.2.4",
"ora": "6.2.0",
"dotenv": "16.0.0"
},
"type": "module",
"engines": {
"npm": ">=6.0.0"
},
"license": "UNLICENSED"
}