This repository was 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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