diff --git a/.gitignore b/.gitignore index 9daa824..91dfed8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .DS_Store -node_modules +node_modules \ No newline at end of file diff --git a/assets/myt.default.yml b/assets/myt.default.yml index ca5ac48..008abb8 100755 --- a/assets/myt.default.yml +++ b/assets/myt.default.yml @@ -6,6 +6,11 @@ mockFunctions: - mockTime - mockUtcTime sumViews: true +defaultDefiner: root@localhost +deprecMarkRegex: __$ +deprecCommentRegex: ^@deprecated [0-9]{4}-[0-9]{2}-[0-9]{2} +deprecDateRegex: '[0-9]{4}-[0-9]{2}-[0-9]{2}' +deprecRetentionPeriod: 60 # Days privileges: userTable: global_priv userWhere: >- diff --git a/myt-clean.js b/myt-clean.js index db37150..a83c2ec 100644 --- a/myt-clean.js +++ b/myt-clean.js @@ -10,7 +10,7 @@ class Clean extends Command { static usage = { description: 'Cleans old applied versions', params: { - purge: 'Wether to remove non-existent scripts from DB log' + purge: 'Whether to remove non-existent scripts from DB log' } }; diff --git a/myt-create.js b/myt-create.js index 1754d9a..84f282b 100755 --- a/myt-create.js +++ b/myt-create.js @@ -43,7 +43,7 @@ class Create extends Command { const params = { schema, name, - definer: 'root@localhost' + definer: opts.defaultDefiner }; switch (opts.type) { diff --git a/myt-dump.js b/myt-dump.js index 19b13f0..4c447c1 100644 --- a/myt-dump.js +++ b/myt-dump.js @@ -9,7 +9,7 @@ class Dump extends Command { description: 'Dumps structure and fixtures from remote', params: { lock: 'Whether to lock tables on dump', - triggers: 'Wether to include triggers into dump' + triggers: 'Whether to include triggers into dump' }, operand: 'remote' }; diff --git a/myt-push.js b/myt-push.js index 041baf7..f87b9d4 100644 --- a/myt-push.js +++ b/myt-push.js @@ -15,9 +15,9 @@ class Push extends Command { description: 'Apply changes into database', params: { force: 'Answer yes to all questions', - commit: 'Wether to save the commit SHA into database', + commit: 'Whether to save the commit SHA into database', sums: 'Save SHA sums of pushed objects', - triggers: 'Wether to exclude triggers, used to generate local DB' + triggers: 'Whether to exclude triggers, used to generate local DB' }, operand: 'remote' }; diff --git a/myt-version.js b/myt-version.js index 219bcaf..2b3ae16 100644 --- a/myt-version.js +++ b/myt-version.js @@ -1,6 +1,7 @@ const Myt = require('./myt'); const Command = require('./lib/command'); const fs = require('fs-extra'); +const SqlString = require('sqlstring'); /** * Creates a new version. @@ -9,18 +10,23 @@ class Version extends Command { static usage = { description: 'Creates a new version', params: { - name: 'Name for the new version' + name: 'Name for the new version', + deprecate: 'Whether to generate sql to delete deprecated objects' }, operand: 'name' }; static opts = { alias: { - name: 'n' + name: 'n', + deprecate: 'p' }, string: [ 'name' ], + boolean: [ + 'deprecate' + ], default: { remote: 'production' } @@ -36,7 +42,8 @@ class Version extends Command { }, versionCreated: function(versionName) { console.log(`New version created: ${versionName}`); - } + }, + deprecate: 'Generating SQL for deprecated objects deletion.' }; async run(myt, opts) { @@ -121,10 +128,16 @@ class Version extends Command { [opts.code, newVersion] ); await fs.mkdir(newVersionDir); - await fs.writeFile( - `${newVersionDir}/00-firstScript.sql`, - '-- Place your SQL code here\n' - ); + + if (opts.deprecate) { + this.emit('deprecate'); + await deprecate(conn, opts, newVersionDir); + } else + await fs.writeFile( + `${newVersionDir}/00-firstScript.sql`, + '-- Place your SQL code here\n' + ); + this.emit('versionCreated', versionFolder); await conn.query('COMMIT'); @@ -137,6 +150,105 @@ class Version extends Command { } } +async function deprecate(conn, opts, newVersionDir) { + const now = new Date(); + const minDeprecDate = new Date(now.getTime() - opts.deprecRetentionPeriod * 24 * 60 * 60 * 1000); + const deprecMarkRegex = opts.deprecMarkRegex; + const deprecCommentRegex = opts.deprecCommentRegex; + const deprecDateRegex = opts.deprecDateRegex; + const filePath = `${newVersionDir}/00-deprecate.sql`; + + // Generate the drops of the primary keys + const [primaryKeys] = await conn.query(` + SELECT c.TABLE_SCHEMA 'schema', c.TABLE_NAME 'table' + FROM information_schema.COLUMNS c + LEFT JOIN information_schema.VIEWS v ON v.TABLE_SCHEMA = c.TABLE_SCHEMA + AND v.TABLE_NAME = c.TABLE_NAME + JOIN information_schema.STATISTICS s ON s.TABLE_SCHEMA = c.TABLE_SCHEMA + AND s.TABLE_NAME = c.TABLE_NAME + AND s.COLUMN_NAME = c.COLUMN_NAME + WHERE c.COLUMN_NAME REGEXP ? COLLATE utf8mb4_unicode_ci + AND c.COLUMN_COMMENT REGEXP ? COLLATE utf8mb4_unicode_ci + AND REGEXP_SUBSTR(c.COLUMN_COMMENT, ? COLLATE utf8mb4_unicode_ci) < ? + AND v.TABLE_NAME IS NULL + AND s.INDEX_NAME = 'PRIMARY' + `, [deprecMarkRegex, deprecCommentRegex, deprecDateRegex, minDeprecDate]); + + primaryKeys.map(async row => { + await fs.appendFile( + filePath, + 'ALTER TABLE ' + SqlString.escapeId(row.schema, true) + '.' + + SqlString.escapeId(row.table, true) + ' DROP PRIMARY KEY;\n' + ); + }); + + // Generate the drops of the foreign keys + const [foreignKeys] = await conn.query(` + SELECT c.TABLE_SCHEMA 'schema', c.TABLE_NAME 'table', kcu.CONSTRAINT_NAME 'constraint' + FROM information_schema.COLUMNS c + LEFT JOIN information_schema.VIEWS v ON v.TABLE_SCHEMA = c.TABLE_SCHEMA + AND v.TABLE_NAME = c.TABLE_NAME + JOIN information_schema.KEY_COLUMN_USAGE kcu ON kcu.TABLE_SCHEMA = c.TABLE_SCHEMA + AND kcu.TABLE_NAME = c.TABLE_NAME + AND kcu.COLUMN_NAME = c.COLUMN_NAME + WHERE c.COLUMN_NAME REGEXP ? COLLATE utf8mb4_unicode_ci + AND c.COLUMN_COMMENT REGEXP ? COLLATE utf8mb4_unicode_ci + AND REGEXP_SUBSTR(c.COLUMN_COMMENT, ? COLLATE utf8mb4_unicode_ci) < ? + AND v.TABLE_NAME IS NULL + AND kcu.REFERENCED_COLUMN_NAME IS NOT NULL + `, [deprecMarkRegex, deprecCommentRegex, deprecDateRegex, minDeprecDate]); + + foreignKeys.map(async row => { + await fs.appendFile( + filePath, + 'ALTER TABLE ' + SqlString.escapeId(row.schema, true) + '.' + + SqlString.escapeId(row.table, true) + ' DROP FOREIGN KEY ' + + SqlString.escapeId(row.constraint, true) + ';\n' + ); + }); + + // Generate the drops of the columns + const [columns] = await conn.query(` + SELECT c.TABLE_SCHEMA 'schema', c.TABLE_NAME 'table', c.COLUMN_NAME 'column' + FROM information_schema.COLUMNS c + LEFT JOIN information_schema.VIEWS v ON v.TABLE_SCHEMA = c.TABLE_SCHEMA + AND v.TABLE_NAME = c.TABLE_NAME + LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu ON kcu.TABLE_SCHEMA = c.TABLE_SCHEMA + AND kcu.TABLE_NAME = c.TABLE_NAME + AND kcu.COLUMN_NAME = c.COLUMN_NAME + WHERE c.COLUMN_NAME REGEXP ? COLLATE utf8mb4_unicode_ci + AND c.COLUMN_COMMENT REGEXP ? COLLATE utf8mb4_unicode_ci + AND REGEXP_SUBSTR(c.COLUMN_COMMENT, ? COLLATE utf8mb4_unicode_ci) { + await fs.appendFile( + filePath, + 'ALTER TABLE ' + SqlString.escapeId(row.schema, true) + '.' + + SqlString.escapeId(row.table, true) + ' DROP COLUMN ' + + SqlString.escapeId(row.column, true) + ';\n' + ); + }); + + // Generate the drops of the tables + const [tables] = await conn.query(` + SELECT TABLE_SCHEMA 'schema', TABLE_NAME 'table' + FROM information_schema.TABLES + WHERE TABLE_NAME REGEXP ? COLLATE utf8mb4_unicode_ci + AND TABLE_COMMENT REGEXP ? COLLATE utf8mb4_unicode_ci + AND REGEXP_SUBSTR(TABLE_COMMENT, ? COLLATE utf8mb4_unicode_ci) < ? + `, [deprecMarkRegex, deprecCommentRegex, deprecDateRegex, minDeprecDate]); + + tables.map(async row => { + await fs.appendFile( + filePath, + 'DROP TABLE ' + SqlString.escapeId(row.schema, true) + '.' + + SqlString.escapeId(row.table, true) + ';\n' + ); + }); +} + function randomName() { const color = random(colors); let plant = random(plants); diff --git a/myt.js b/myt.js index 128c3b8..5b4007c 100755 --- a/myt.js +++ b/myt.js @@ -19,7 +19,7 @@ class Myt { params: { remote: 'Name of remote to use', workspace: 'The base directory of the project', - debug: 'Wether to enable debug mode', + debug: 'Whether to enable debug mode', version: 'Display the version number and exit', help: 'Display this help message' } diff --git a/package.json b/package.json index 8171d65..b849b3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@verdnatura/myt", - "version": "1.6.9", + "version": "1.6.10", "author": "Verdnatura Levante SL", "description": "MySQL version control", "license": "GPL-3.0",