Compare commits

..

24 Commits

Author SHA1 Message Date
Guillermo Bonet 6318a58f24 fix: refs #7759 mockDate 2025-02-21 10:30:41 +01:00
Alex Moreno f05cc498cc 1.6.12 2024-10-03 12:27:15 +00:00
Alex Moreno 2b6a97c243 Merge pull request 'feat: use the connection if it is passed as a parameter' (!7) from useCustomNetwork into master
Reviewed-on: #7
Reviewed-by: Juan Ferrer <juan@verdnatura.es>
2024-10-03 12:25:36 +00:00
Alex Moreno 5e6353688f feat: use the connection if it is passed as a parameter 2024-10-03 12:12:47 +02:00
Juan Ferrer 9416617800 fix: refs #7562 File execution attributes fixed 2024-09-12 13:03:24 +02:00
Guillermo Bonet 105227408c 1.6.10 2024-09-12 10:18:07 +02:00
Guillermo Bonet 0bf241600b 1.6.9 2024-09-12 10:17:59 +02:00
Guillermo Bonet 71e8329dfc Merge pull request 'feat: refs #7562 deleteDeprecatedObjects' (!6) from 7562-deleteDeprecatedObjects into master
Reviewed-on: #6
Reviewed-by: Juan Ferrer <juan@verdnatura.es>
2024-09-12 08:06:15 +00:00
Guillermo Bonet 5bc8fb0839 feat: refs #7562 Version increased 2024-09-12 09:48:38 +02:00
Guillermo Bonet d92be7533c feat: refs #7562 Requested changes 2024-09-12 09:46:55 +02:00
Guillermo Bonet ea42017e4f feat: refs #7562 Requested changes 2024-09-12 09:42:10 +02:00
Guillermo Bonet 5ac41532d9 feat: refs #7562 No sql concat and more 2024-09-06 09:46:24 +02:00
Guillermo Bonet a38cda0ba3 feat: refs #7562 Fixes 2024-09-05 08:18:18 +02:00
Guillermo Bonet a27afbaa53 feat: refs #7562 Fixes 2024-09-04 15:04:45 +02:00
Guillermo Bonet 912719cc08 feat: refs #7562 Added deprecate funcion 2024-09-04 14:50:27 +02:00
Guillermo Bonet dfa9570ea4 Merge pull request 'feat: refs #5846 Added eventScheduler function' (!4) from 5846-eventScheduler into master
Reviewed-on: #4
Reviewed-by: Juan Ferrer <juan@verdnatura.es>
2024-03-21 06:46:25 +00:00
Guillermo Bonet 3324175483 feat: refs #5846 Requested changes 2024-03-20 12:20:10 +01:00
Guillermo Bonet 823de4889f feat: refs #5846 Requested changes 2024-03-11 13:05:40 +01:00
Guillermo Bonet 7d68eaa3ac feat: refs #5846 Requested changes 2024-03-11 09:25:43 +01:00
Guillermo Bonet dfa1db1432 feat: refs #5846 Added eventScheduler function 2024-03-05 07:44:07 +01:00
Juan Ferrer c02eed742d fix(push): wrong version name kills command 2024-02-22 10:10:29 +01:00
Juan Ferrer 79a374d460 fix(dump): refs #5483 ini path should not include subdir 2024-02-15 11:05:53 +01:00
Juan Ferrer 6eb451ecaf fix(run): refs#6706 keep option to keep container on failure 2024-02-07 09:51:32 +01:00
Juan Ferrer 26ac3e995a fix(run): refs#6706 Remove container on failure, network param fix 2024-02-06 22:21:31 +01:00
14 changed files with 257 additions and 109 deletions

View File

@ -6,6 +6,14 @@ mockFunctions:
- mockTime - mockTime
- mockUtcTime - mockUtcTime
sumViews: true sumViews: true
defaultDefiner: root@localhost
localRemotes:
- local
- docker
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: privileges:
userTable: global_priv userTable: global_priv
userWhere: >- userWhere: >-

View File

@ -83,7 +83,7 @@ module.exports = class Dumper {
} }
async runDump(command, args) { async runDump(command, args) {
const iniPath = path.join(this.opts.subdir || '', 'remotes', this.opts.iniFile); const iniPath = path.join('remotes', this.opts.iniFile);
const myArgs = [ const myArgs = [
`--defaults-file=${iniPath}` `--defaults-file=${iniPath}`
]; ];

2
myt-clean.js Normal file → Executable file
View File

@ -10,7 +10,7 @@ class Clean extends Command {
static usage = { static usage = {
description: 'Cleans old applied versions', description: 'Cleans old applied versions',
params: { params: {
purge: 'Wether to remove non-existent scripts from DB log' purge: 'Whether to remove non-existent scripts from DB log'
} }
}; };

View File

@ -43,7 +43,7 @@ class Create extends Command {
const params = { const params = {
schema, schema,
name, name,
definer: 'root@localhost' definer: opts.defaultDefiner
}; };
switch (opts.type) { switch (opts.type) {

2
myt-dump.js Normal file → Executable file
View File

@ -9,7 +9,7 @@ class Dump extends Command {
description: 'Dumps structure and fixtures from remote', description: 'Dumps structure and fixtures from remote',
params: { params: {
lock: 'Whether to lock tables on dump', lock: 'Whether to lock tables on dump',
triggers: 'Wether to include triggers into dump' triggers: 'Whether to include triggers into dump'
}, },
operand: 'remote' operand: 'remote'
}; };

0
myt-fixtures.js Normal file → Executable file
View File

29
myt-push.js Normal file → Executable file
View File

@ -15,9 +15,9 @@ class Push extends Command {
description: 'Apply changes into database', description: 'Apply changes into database',
params: { params: {
force: 'Answer yes to all questions', 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', 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' operand: 'remote'
}; };
@ -156,7 +156,17 @@ class Push extends Command {
throw new Error(`Cannot obtain exclusive lock, used by connection ${isUsed}`); throw new Error(`Cannot obtain exclusive lock, used by connection ${isUsed}`);
} }
const [[scheduler]] = await conn.query(`SELECT @@event_scheduler state`);
if (scheduler.state === 'ON') await eventScheduler(false);
async function eventScheduler(isActive) {
await conn.query(
`SET GLOBAL event_scheduler = ${isActive ? 'ON' : 'OFF'}`
);
}
async function releaseLock() { async function releaseLock() {
if (scheduler.state === 'ON') await eventScheduler(true);
await conn.query(`DO RELEASE_LOCK('myt_push')`); await conn.query(`DO RELEASE_LOCK('myt_push')`);
} }
@ -244,13 +254,15 @@ class Push extends Command {
let apply = false; let apply = false;
if (!version) if (!version) {
this.emit('version', version, versionDir, 'wrongDirectory'); this.emit('version', version, versionDir, 'wrongDirectory');
else if (version.number.length != dbVersion.number.length) continue;
} else if (version.number.length != dbVersion.number.length) {
this.emit('version', version, versionDir, 'badVersion'); this.emit('version', version, versionDir, 'badVersion');
else continue;
apply = version.apply; }
apply = version.apply;
if (apply) showLog = true; if (apply) showLog = true;
if (showLog) this.emit('version', version, versionDir); if (showLog) this.emit('version', version, versionDir);
if (!apply) continue; if (!apply) continue;
@ -359,9 +371,12 @@ class Push extends Command {
const oldSql = await engine.fetchRoutine(type, schema, name); const oldSql = await engine.fetchRoutine(type, schema, name);
const oldSum = engine.getShaSum(type, schema, name); const oldSum = engine.getShaSum(type, schema, name);
const localRemote = opts.remote == null
|| opts.localRemotes?.indexOf(opts.remote) !== -1;
const isMockFn = type == 'function' const isMockFn = type == 'function'
&& schema == opts.versionSchema && schema == opts.versionSchema
&& opts.remote == 'local' && localRemote
&& opts.mockDate && opts.mockDate
&& opts.mockFunctions && opts.mockFunctions
&& opts.mockFunctions.indexOf(name) !== -1; && opts.mockFunctions.indexOf(name) !== -1;

22
myt-run.js Normal file → Executable file
View File

@ -20,7 +20,8 @@ class Run extends Command {
ci: 'Workaround for continuous integration system', ci: 'Workaround for continuous integration system',
network: 'Docker network to attach container to', network: 'Docker network to attach container to',
random: 'Whether to use a random container name and port', random: 'Whether to use a random container name and port',
tmpfs: 'Whether to use tmpfs mount for MySQL data' tmpfs: 'Whether to use tmpfs mount for MySQL data',
keep: 'Keep container on failure'
} }
}; };
@ -29,12 +30,14 @@ class Run extends Command {
ci: 'c', ci: 'c',
network: 'n', network: 'n',
random: 'r', random: 'r',
tmpfs: 't' tmpfs: 't',
keep: 'k'
}, },
boolean: [ boolean: [
'ci', 'ci',
'random', 'random',
'tmpfs' 'tmpfs',
'keep'
] ]
}; };
@ -118,15 +121,18 @@ class Run extends Command {
detach: true detach: true
}); });
const ct = await docker.run(opts.code, null, runOptions); const ct = await docker.run(opts.code, null, runOptions);
try {
const server = new Server(ct, dbConfig); const server = new Server(ct, dbConfig);
if (isRandom) { const useCustom = opts.ci || opts.network
if (isRandom || useCustom) {
try { try {
const netSettings = await ct.inspect({ const netSettings = await ct.inspect({
format: '{{json .NetworkSettings}}' format: '{{json .NetworkSettings}}'
}); });
if (opts.ci) { if (useCustom) {
dbConfig.host = opts.network dbConfig.host = opts.network
? netSettings.Networks[opts.network].IPAddress ? netSettings.Networks[opts.network].IPAddress
: netSettings.Gateway; : netSettings.Gateway;
@ -204,6 +210,12 @@ class Run extends Command {
await conn.end(); await conn.end();
return server; return server;
} catch (err) {
try {
if (!opts.keep) await ct.rm({force: true});
} catch (e) {}
throw err;
}
} }
} }

0
myt-start.js Normal file → Executable file
View File

118
myt-version.js Normal file → Executable file
View File

@ -1,6 +1,7 @@
const Myt = require('./myt'); const Myt = require('./myt');
const Command = require('./lib/command'); const Command = require('./lib/command');
const fs = require('fs-extra'); const fs = require('fs-extra');
const SqlString = require('sqlstring');
/** /**
* Creates a new version. * Creates a new version.
@ -9,18 +10,23 @@ class Version extends Command {
static usage = { static usage = {
description: 'Creates a new version', description: 'Creates a new version',
params: { params: {
name: 'Name for the new version' name: 'Name for the new version',
deprecate: 'Whether to generate sql to delete deprecated objects'
}, },
operand: 'name' operand: 'name'
}; };
static opts = { static opts = {
alias: { alias: {
name: 'n' name: 'n',
deprecate: 'p'
}, },
string: [ string: [
'name' 'name'
], ],
boolean: [
'deprecate'
],
default: { default: {
remote: 'production' remote: 'production'
} }
@ -36,7 +42,8 @@ class Version extends Command {
}, },
versionCreated: function(versionName) { versionCreated: function(versionName) {
console.log(`New version created: ${versionName}`); console.log(`New version created: ${versionName}`);
} },
deprecate: 'Generating SQL for deprecated objects deletion.'
}; };
async run(myt, opts) { async run(myt, opts) {
@ -121,10 +128,16 @@ class Version extends Command {
[opts.code, newVersion] [opts.code, newVersion]
); );
await fs.mkdir(newVersionDir); await fs.mkdir(newVersionDir);
if (opts.deprecate) {
this.emit('deprecate');
await deprecate(conn, opts, newVersionDir);
} else
await fs.writeFile( await fs.writeFile(
`${newVersionDir}/00-firstScript.sql`, `${newVersionDir}/00-firstScript.sql`,
'-- Place your SQL code here\n' '-- Place your SQL code here\n'
); );
this.emit('versionCreated', versionFolder); this.emit('versionCreated', versionFolder);
await conn.query('COMMIT'); 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) <?
AND v.TABLE_NAME IS NULL
`, [deprecMarkRegex, deprecCommentRegex, deprecDateRegex, minDeprecDate]);
columns.map(async row => {
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() { function randomName() {
const color = random(colors); const color = random(colors);
let plant = random(plants); let plant = random(plants);

2
myt.js
View File

@ -19,7 +19,7 @@ class Myt {
params: { params: {
remote: 'Name of remote to use', remote: 'Name of remote to use',
workspace: 'The base directory of the project', 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', version: 'Display the version number and exit',
help: 'Display this help message' help: 'Display this help message'
} }

5
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "@verdnatura/myt", "name": "@verdnatura/myt",
"version": "1.6.3", "version": "1.6.13",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@verdnatura/myt", "name": "@verdnatura/myt",
"version": "1.6.3", "version": "1.6.13",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@sqltools/formatter": "^1.2.5", "@sqltools/formatter": "^1.2.5",
@ -1169,6 +1169,7 @@
"resolved": "https://registry.npmjs.org/nodegit/-/nodegit-0.27.0.tgz", "resolved": "https://registry.npmjs.org/nodegit/-/nodegit-0.27.0.tgz",
"integrity": "sha512-E9K4gPjWiA0b3Tx5lfWCzG7Cvodi2idl3V5UD2fZrOrHikIfrN7Fc2kWLtMUqqomyoToYJLeIC8IV7xb1CYRLA==", "integrity": "sha512-E9K4gPjWiA0b3Tx5lfWCzG7Cvodi2idl3V5UD2fZrOrHikIfrN7Fc2kWLtMUqqomyoToYJLeIC8IV7xb1CYRLA==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT",
"dependencies": { "dependencies": {
"fs-extra": "^7.0.0", "fs-extra": "^7.0.0",
"got": "^10.7.0", "got": "^10.7.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@verdnatura/myt", "name": "@verdnatura/myt",
"version": "1.6.3", "version": "1.6.13",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "MySQL version control", "description": "MySQL version control",
"license": "GPL-3.0", "license": "GPL-3.0",