This repository has been archived by the owner on Oct 7, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 61
feat(migration-scripts) add sql-blueprinting script #107
Open
FotisVasilopoulos
wants to merge
1
commit into
strapi:main
Choose a base branch
from
FotisVasilopoulos:sql-blueprinting
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
*.env | ||
package-lock.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
- 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will update the README