@verdnatura/myt 1.5.19 → 1.5.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +2 -2
- package/LICENSE +17 -17
- package/README.md +278 -278
- package/assets/db.ini +5 -5
- package/assets/myt.default.yml +11 -11
- package/assets/structure.sql +24 -24
- package/cli.js +4 -4
- package/exporters/event.ejs +18 -18
- package/exporters/event.js +31 -31
- package/exporters/event.sql +19 -19
- package/exporters/function.ejs +17 -17
- package/exporters/function.js +27 -27
- package/exporters/function.sql +16 -16
- package/exporters/procedure.ejs +11 -11
- package/exporters/procedure.js +23 -23
- package/exporters/procedure.sql +14 -14
- package/exporters/trigger.ejs +4 -4
- package/exporters/trigger.js +10 -10
- package/exporters/trigger.sql +13 -13
- package/exporters/view.ejs +7 -7
- package/exporters/view.js +29 -29
- package/exporters/view.sql +12 -12
- package/lib/command.js +13 -13
- package/lib/conn.js +141 -141
- package/lib/docker.js +133 -133
- package/lib/dumper.js +83 -83
- package/lib/exporter-engine.js +112 -112
- package/lib/exporter.js +86 -86
- package/lib/repo.js +29 -29
- package/lib/server.js +71 -71
- package/lib/util.js +7 -7
- package/myt-clean.js +58 -58
- package/myt-create.js +74 -74
- package/myt-dump.js +75 -75
- package/myt-fixtures.js +29 -29
- package/myt-init.js +36 -36
- package/myt-pull.js +183 -183
- package/myt-push.js +568 -568
- package/myt-run.js +177 -177
- package/myt-start.js +54 -54
- package/myt-version.js +218 -218
- package/myt.js +361 -361
- package/package.json +41 -41
- package/server/Dockerfile.base +1 -1
- package/server/Dockerfile.client +14 -14
- package/server/Dockerfile.dump +16 -16
- package/server/Dockerfile.server +25 -25
- package/server/docker-dump.sh +4 -4
- package/server/docker-fixtures.sh +4 -4
- package/server/docker-import.sh +6 -6
- package/server/docker-init.sh +20 -20
- package/server/docker-start.sh +10 -10
- package/server/docker.cnf +9 -9
- package/template/dump/Dockerfile.base +1 -1
- package/template/dump/afterDump.sql +1 -1
- package/template/dump/beforeDump.sql +1 -1
- package/template/dump/fixtures.sql +1 -1
- package/template/myt.config.yml +11 -11
- package/template/package.json +13 -13
- package/template/remotes/docker.ini +4 -4
- package/template/remotes/local.ini +5 -5
- package/template/routines/README.md +1 -1
- package/template/versions/00001-firstVersion/00-firstScript.sql +1 -1
- package/template/versions/README.md +1 -1
package/myt-push.js
CHANGED
|
@@ -1,568 +1,568 @@
|
|
|
1
|
-
const Myt = require('./myt');
|
|
2
|
-
const Command = require('./lib/command');
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const nodegit = require('nodegit');
|
|
5
|
-
const ExporterEngine = require('./lib/exporter-engine');
|
|
6
|
-
const connExt = require('./lib/conn');
|
|
7
|
-
const repoExt = require('./lib/repo');
|
|
8
|
-
const SqlString = require('sqlstring');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Pushes changes to remote.
|
|
12
|
-
*/
|
|
13
|
-
class Push extends Command {
|
|
14
|
-
static usage = {
|
|
15
|
-
description: 'Apply changes into database',
|
|
16
|
-
params: {
|
|
17
|
-
force: 'Answer yes to all questions',
|
|
18
|
-
commit: 'Wether to save the commit SHA into database',
|
|
19
|
-
sums: 'Save SHA sums of pushed objects',
|
|
20
|
-
triggers: 'Wether to exclude triggers, used to generate local DB'
|
|
21
|
-
},
|
|
22
|
-
operand: 'remote'
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
static opts = {
|
|
26
|
-
alias: {
|
|
27
|
-
force: 'f',
|
|
28
|
-
commit: 'c',
|
|
29
|
-
sums: 's',
|
|
30
|
-
triggers: 't'
|
|
31
|
-
},
|
|
32
|
-
boolean: [
|
|
33
|
-
'force',
|
|
34
|
-
'commit',
|
|
35
|
-
'sums',
|
|
36
|
-
'triggers'
|
|
37
|
-
]
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
async run(myt, opts) {
|
|
41
|
-
const conn = await myt.dbConnect();
|
|
42
|
-
this.conn = conn;
|
|
43
|
-
|
|
44
|
-
if (opts.remote == 'local')
|
|
45
|
-
opts.commit = true;
|
|
46
|
-
|
|
47
|
-
// Obtain exclusive lock
|
|
48
|
-
|
|
49
|
-
const [[row]] = await conn.query(
|
|
50
|
-
`SELECT GET_LOCK('myt_push', 30) getLock`);
|
|
51
|
-
|
|
52
|
-
const pingTimeout = setInterval(async() => {
|
|
53
|
-
try {
|
|
54
|
-
await conn.ping()
|
|
55
|
-
} catch (e) {}
|
|
56
|
-
}, 60 * 1000);
|
|
57
|
-
|
|
58
|
-
if (!row.getLock) {
|
|
59
|
-
let isUsed = 0;
|
|
60
|
-
|
|
61
|
-
if (row.getLock == 0) {
|
|
62
|
-
const [[row]] = await conn.query(
|
|
63
|
-
`SELECT IS_USED_LOCK('myt_push') isUsed`);
|
|
64
|
-
isUsed = row.isUsed;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
throw new Error(`Cannot obtain exclusive lock, used by connection ${isUsed}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function releaseLock() {
|
|
71
|
-
await conn.query(`DO RELEASE_LOCK('myt_push')`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
await this.push(myt, opts, conn);
|
|
76
|
-
} catch(err) {
|
|
77
|
-
try {
|
|
78
|
-
await releaseLock();
|
|
79
|
-
} catch (e) {}
|
|
80
|
-
throw err;
|
|
81
|
-
} finally {
|
|
82
|
-
clearInterval(pingTimeout);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
await releaseLock();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async push(myt, opts, conn) {
|
|
89
|
-
const pushConn = await myt.createConnection();
|
|
90
|
-
|
|
91
|
-
// Get database version
|
|
92
|
-
|
|
93
|
-
const version = await myt.fetchDbVersion() || {};
|
|
94
|
-
|
|
95
|
-
console.log(
|
|
96
|
-
`Database information:`
|
|
97
|
-
+ `\n -> Version: ${version.number}`
|
|
98
|
-
+ `\n -> Commit: ${version.gitCommit}`
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
if (!version.number)
|
|
102
|
-
version.number = String('0').padStart(opts.versionDigits, '0');
|
|
103
|
-
if (!/^[0-9]*$/.test(version.number))
|
|
104
|
-
throw new Error('Wrong database version');
|
|
105
|
-
|
|
106
|
-
// Prevent push to production by mistake
|
|
107
|
-
|
|
108
|
-
if (opts.remote == 'production') {
|
|
109
|
-
console.log(
|
|
110
|
-
'\n ( ( ) ( ( ) ) '
|
|
111
|
-
+ '\n )\\ ))\\ ) ( /( )\\ ) ( ))\\ ) ( /( ( /( '
|
|
112
|
-
+ '\n(()/(()/( )\\()|()/( ( )\\ ) /(()/( )\\()) )\\())'
|
|
113
|
-
+ '\n /(_))(_)|(_)\\ /(_)) )\\ (((_) ( )(_))(_)|(_)\\ ((_)\\ '
|
|
114
|
-
+ '\n(_))(_)) ((_|_))_ _ ((_))\\___(_(_()|__)) ((_) _((_)'
|
|
115
|
-
+ '\n| _ \\ _ \\ / _ \\| \\| | | ((/ __|_ _|_ _| / _ \\| \\| |'
|
|
116
|
-
+ '\n| _/ /| (_) | |) | |_| || (__ | | | | | (_) | . |'
|
|
117
|
-
+ '\n|_| |_|_\\ \\___/|___/ \\___/ \\___| |_| |___| \\___/|_|\\_|'
|
|
118
|
-
+ '\n'
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
if (!opts.force) {
|
|
122
|
-
const readline = require('readline');
|
|
123
|
-
const rl = readline.createInterface({
|
|
124
|
-
input: process.stdin,
|
|
125
|
-
output: process.stdout
|
|
126
|
-
});
|
|
127
|
-
const answer = await new Promise(resolve => {
|
|
128
|
-
rl.question('Are you sure? (Default: no) [yes|no] ', resolve);
|
|
129
|
-
});
|
|
130
|
-
rl.close();
|
|
131
|
-
|
|
132
|
-
if (answer !== 'yes')
|
|
133
|
-
throw new Error('Changes aborted');
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Apply versions
|
|
138
|
-
|
|
139
|
-
console.log('Applying versions.');
|
|
140
|
-
|
|
141
|
-
let nChanges = 0;
|
|
142
|
-
let silent = true;
|
|
143
|
-
const versionsDir = opts.versionsDir;
|
|
144
|
-
|
|
145
|
-
function logVersion(version, name, error) {
|
|
146
|
-
console.log('', version.bold, name);
|
|
147
|
-
}
|
|
148
|
-
function logScript(type, message, error) {
|
|
149
|
-
console.log(' ', type.bold, message);
|
|
150
|
-
}
|
|
151
|
-
function isUndoScript(script) {
|
|
152
|
-
return /\.undo\.sql$/.test(script);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const skipFiles = new Set([
|
|
156
|
-
'README.md',
|
|
157
|
-
'.archive'
|
|
158
|
-
]);
|
|
159
|
-
|
|
160
|
-
if (await fs.pathExists(versionsDir)) {
|
|
161
|
-
const versionDirs = await fs.readdir(versionsDir);
|
|
162
|
-
|
|
163
|
-
for (const versionDir of versionDirs) {
|
|
164
|
-
if (skipFiles.has(versionDir)) continue;
|
|
165
|
-
|
|
166
|
-
const dirVersion = myt.parseVersionDir(versionDir);
|
|
167
|
-
if (!dirVersion) {
|
|
168
|
-
logVersion('[?????]'.yellow, versionDir,
|
|
169
|
-
`Wrong directory name.`
|
|
170
|
-
);
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const versionNumber = dirVersion.number;
|
|
175
|
-
const versionName = dirVersion.name;
|
|
176
|
-
|
|
177
|
-
if (versionNumber.length != version.number.length) {
|
|
178
|
-
logVersion('[*****]'.gray, versionDir,
|
|
179
|
-
`Bad version length, should have ${version.number.length} characters.`
|
|
180
|
-
);
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const scriptsDir = `${versionsDir}/${versionDir}`;
|
|
185
|
-
const scripts = await fs.readdir(scriptsDir);
|
|
186
|
-
|
|
187
|
-
const [versionLog] = await conn.query(
|
|
188
|
-
`SELECT file FROM versionLog
|
|
189
|
-
WHERE code = ?
|
|
190
|
-
AND number = ?
|
|
191
|
-
AND errorNumber IS NULL`,
|
|
192
|
-
[opts.code, versionNumber]
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
for (const script of scripts)
|
|
196
|
-
if (!isUndoScript(script)
|
|
197
|
-
&& versionLog.findIndex(x => x.file == script) === -1) {
|
|
198
|
-
silent = false;
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (silent) continue;
|
|
203
|
-
logVersion(`[${versionNumber}]`.cyan, versionName);
|
|
204
|
-
|
|
205
|
-
for (const script of scripts) {
|
|
206
|
-
if (!/^[0-9]{2}-[a-zA-Z0-9_]+(.undo)?\.sql$/.test(script)) {
|
|
207
|
-
logScript('[W]'.yellow, script, `Wrong file name.`);
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
if (isUndoScript(script))
|
|
211
|
-
continue;
|
|
212
|
-
|
|
213
|
-
const [[row]] = await conn.query(
|
|
214
|
-
`SELECT errorNumber FROM versionLog
|
|
215
|
-
WHERE code = ?
|
|
216
|
-
AND number = ?
|
|
217
|
-
AND file = ?`,
|
|
218
|
-
[
|
|
219
|
-
opts.code,
|
|
220
|
-
versionNumber,
|
|
221
|
-
script
|
|
222
|
-
]
|
|
223
|
-
);
|
|
224
|
-
const apply = !row || row.errorNumber;
|
|
225
|
-
const actionMsg = apply ? '[+]'.green : '[I]'.blue;
|
|
226
|
-
|
|
227
|
-
logScript(actionMsg, script);
|
|
228
|
-
if (!apply) continue;
|
|
229
|
-
|
|
230
|
-
let err;
|
|
231
|
-
try {
|
|
232
|
-
await connExt.queryFromFile(pushConn,
|
|
233
|
-
`${scriptsDir}/${script}`);
|
|
234
|
-
} catch (e) {
|
|
235
|
-
err = e;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
await conn.query(
|
|
239
|
-
`INSERT INTO versionLog
|
|
240
|
-
SET code = ?,
|
|
241
|
-
number = ?,
|
|
242
|
-
file = ?,
|
|
243
|
-
user = USER(),
|
|
244
|
-
updated = NOW(),
|
|
245
|
-
errorNumber = ?,
|
|
246
|
-
errorMessage = ?
|
|
247
|
-
ON DUPLICATE KEY UPDATE
|
|
248
|
-
updated = VALUES(updated),
|
|
249
|
-
user = VALUES(user),
|
|
250
|
-
errorNumber = VALUES(errorNumber),
|
|
251
|
-
errorMessage = VALUES(errorMessage)`,
|
|
252
|
-
[
|
|
253
|
-
opts.code,
|
|
254
|
-
versionNumber,
|
|
255
|
-
script,
|
|
256
|
-
err && err.errno,
|
|
257
|
-
err && err.message
|
|
258
|
-
]
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
if (err) throw err;
|
|
262
|
-
nChanges++;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
await this.updateVersion('number', versionNumber);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Apply routines
|
|
270
|
-
|
|
271
|
-
console.log('Applying changed routines.');
|
|
272
|
-
|
|
273
|
-
const gitExists = await fs.pathExists(`${opts.workspace}/.git`);
|
|
274
|
-
|
|
275
|
-
let nRoutines = 0;
|
|
276
|
-
const changes = await this.changedRoutines(version.gitCommit);
|
|
277
|
-
|
|
278
|
-
const routines = [];
|
|
279
|
-
for (const change of changes)
|
|
280
|
-
if (change.isRoutine)
|
|
281
|
-
routines.push([
|
|
282
|
-
change.schema,
|
|
283
|
-
change.name,
|
|
284
|
-
change.type.name
|
|
285
|
-
]);
|
|
286
|
-
|
|
287
|
-
if (routines.length) {
|
|
288
|
-
await conn.query(
|
|
289
|
-
`DROP TEMPORARY TABLE IF EXISTS tProcsPriv`
|
|
290
|
-
);
|
|
291
|
-
await conn.query(
|
|
292
|
-
`CREATE TEMPORARY TABLE tProcsPriv
|
|
293
|
-
ENGINE = MEMORY
|
|
294
|
-
SELECT * FROM mysql.procs_priv
|
|
295
|
-
WHERE (Db, Routine_name, Routine_type) IN (?)`,
|
|
296
|
-
[routines]
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const engine = new ExporterEngine(conn, opts);
|
|
301
|
-
await engine.init();
|
|
302
|
-
|
|
303
|
-
async function finalize() {
|
|
304
|
-
await engine.saveInfo();
|
|
305
|
-
|
|
306
|
-
if (routines.length) {
|
|
307
|
-
await conn.query('FLUSH PRIVILEGES');
|
|
308
|
-
await conn.query(`DROP TEMPORARY TABLE tProcsPriv`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
for (const change of changes)
|
|
313
|
-
try {
|
|
314
|
-
if (opts.trigger && change.type.name === 'TRIGGER')
|
|
315
|
-
continue;
|
|
316
|
-
|
|
317
|
-
const schema = change.schema;
|
|
318
|
-
const name = change.name;
|
|
319
|
-
const type = change.type.name.toLowerCase();
|
|
320
|
-
const fullPath = `${opts.routinesDir}/${change.path}.sql`;
|
|
321
|
-
const exists = await fs.pathExists(fullPath);
|
|
322
|
-
|
|
323
|
-
let newSql;
|
|
324
|
-
if (exists)
|
|
325
|
-
newSql = await fs.readFile(fullPath, 'utf8');
|
|
326
|
-
const oldSql = await engine.fetchRoutine(type, schema, name);
|
|
327
|
-
const oldSum = engine.getShaSum(type, schema, name);
|
|
328
|
-
const isEqual = newSql == oldSql;
|
|
329
|
-
|
|
330
|
-
let statusMsg;
|
|
331
|
-
if (exists && !oldSql)
|
|
332
|
-
statusMsg = '[+]'.green;
|
|
333
|
-
else if (!exists)
|
|
334
|
-
statusMsg = '[-]'.red;
|
|
335
|
-
else
|
|
336
|
-
statusMsg = '[·]'.yellow;
|
|
337
|
-
|
|
338
|
-
let actionMsg;
|
|
339
|
-
if (isEqual)
|
|
340
|
-
actionMsg = '[I]'.blue;
|
|
341
|
-
else
|
|
342
|
-
actionMsg = '[A]'.green;
|
|
343
|
-
|
|
344
|
-
const typeMsg = `[${change.type.abbr}]`[change.type.color];
|
|
345
|
-
console.log('',
|
|
346
|
-
(statusMsg + actionMsg).bold,
|
|
347
|
-
typeMsg.bold,
|
|
348
|
-
change.fullName
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
if (!isEqual) {
|
|
352
|
-
const scapedSchema = SqlString.escapeId(schema, true);
|
|
353
|
-
|
|
354
|
-
if (exists) {
|
|
355
|
-
if (change.type.name === 'VIEW')
|
|
356
|
-
await pushConn.query(`USE ${scapedSchema}`);
|
|
357
|
-
|
|
358
|
-
await connExt.multiQuery(pushConn, newSql);
|
|
359
|
-
|
|
360
|
-
if (change.isRoutine) {
|
|
361
|
-
await conn.query(
|
|
362
|
-
`INSERT IGNORE INTO mysql.procs_priv
|
|
363
|
-
SELECT * FROM tProcsPriv
|
|
364
|
-
WHERE Db = ?
|
|
365
|
-
AND Routine_name = ?
|
|
366
|
-
AND Routine_type = ?`,
|
|
367
|
-
[schema, name, change.type.name]
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (opts.sums || oldSum || (opts.sumViews && type === 'view'))
|
|
372
|
-
await engine.fetchShaSum(type, schema, name);
|
|
373
|
-
} else {
|
|
374
|
-
const escapedName =
|
|
375
|
-
scapedSchema + '.' +
|
|
376
|
-
SqlString.escapeId(name, true);
|
|
377
|
-
|
|
378
|
-
const query = `DROP ${change.type.name} IF EXISTS ${escapedName}`;
|
|
379
|
-
await pushConn.query(query);
|
|
380
|
-
|
|
381
|
-
engine.deleteShaSum(type, schema, name);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
nRoutines++;
|
|
385
|
-
}
|
|
386
|
-
} catch (err) {
|
|
387
|
-
try {
|
|
388
|
-
await finalize();
|
|
389
|
-
} catch (e) {
|
|
390
|
-
console.error(e);
|
|
391
|
-
}
|
|
392
|
-
throw err;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
await finalize();
|
|
396
|
-
|
|
397
|
-
if (nRoutines > 0) {
|
|
398
|
-
console.log(` -> ${nRoutines} routines have changed.`);
|
|
399
|
-
} else
|
|
400
|
-
console.log(` -> No routines changed.`);
|
|
401
|
-
|
|
402
|
-
if (gitExists && opts.commit) {
|
|
403
|
-
const repo = await nodegit.Repository.open(this.opts.workspace);
|
|
404
|
-
const head = await repo.getHeadCommit();
|
|
405
|
-
|
|
406
|
-
if (head && version.gitCommit !== head.sha())
|
|
407
|
-
await this.updateVersion('gitCommit', head.sha());
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// End
|
|
411
|
-
|
|
412
|
-
await pushConn.end();
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
async updateVersion(column, value) {
|
|
416
|
-
column = SqlString.escapeId(column, true);
|
|
417
|
-
await this.conn.query(
|
|
418
|
-
`INSERT INTO version
|
|
419
|
-
SET code = ?,
|
|
420
|
-
${column} = ?,
|
|
421
|
-
updated = NOW()
|
|
422
|
-
ON DUPLICATE KEY UPDATE
|
|
423
|
-
${column} = VALUES(${column}),
|
|
424
|
-
updated = VALUES(updated)`,
|
|
425
|
-
[
|
|
426
|
-
this.opts.code,
|
|
427
|
-
value
|
|
428
|
-
]
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async changedRoutines(commitSha) {
|
|
433
|
-
const repo = await this.myt.openRepo();
|
|
434
|
-
const changes = [];
|
|
435
|
-
const changesMap = new Map();
|
|
436
|
-
|
|
437
|
-
async function pushChanges(diff) {
|
|
438
|
-
if (!diff) return;
|
|
439
|
-
const patches = await diff.patches();
|
|
440
|
-
|
|
441
|
-
for (const patch of patches) {
|
|
442
|
-
const path = patch.newFile().path();
|
|
443
|
-
const match = path.match(/^routines\/(.+)\.sql$/);
|
|
444
|
-
if (!match) continue;
|
|
445
|
-
|
|
446
|
-
let change = changesMap.get(match[1]);
|
|
447
|
-
if (!change) {
|
|
448
|
-
change = {path: match[1]};
|
|
449
|
-
changes.push(change);
|
|
450
|
-
changesMap.set(match[1], change);
|
|
451
|
-
}
|
|
452
|
-
change.mark = patch.isDeleted() ? '-' : '+';
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const head = await repo.getHeadCommit();
|
|
457
|
-
|
|
458
|
-
if (head && commitSha) {
|
|
459
|
-
let commit;
|
|
460
|
-
let notFound;
|
|
461
|
-
|
|
462
|
-
try {
|
|
463
|
-
commit = await repo.getCommit(commitSha);
|
|
464
|
-
notFound = false;
|
|
465
|
-
} catch (err) {
|
|
466
|
-
if (err.errorFunction == 'Commit.lookup')
|
|
467
|
-
notFound = true;
|
|
468
|
-
else
|
|
469
|
-
throw err;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (notFound) {
|
|
473
|
-
console.warn(`Database commit not found, trying git fetch`.yellow);
|
|
474
|
-
await repo.fetchAll();
|
|
475
|
-
commit = await repo.getCommit(commitSha);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const commitTree = await commit.getTree();
|
|
479
|
-
|
|
480
|
-
const headTree = await head.getTree();
|
|
481
|
-
const diff = await headTree.diff(commitTree);
|
|
482
|
-
await pushChanges(diff);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
await pushChanges(await repoExt.getUnstaged(repo));
|
|
486
|
-
await pushChanges(await repoExt.getStaged(repo));
|
|
487
|
-
|
|
488
|
-
const routines = [];
|
|
489
|
-
for (const change of changes)
|
|
490
|
-
routines.push(new Routine(change));
|
|
491
|
-
|
|
492
|
-
return routines.sort((a, b) => {
|
|
493
|
-
if (b.mark != a.mark)
|
|
494
|
-
return b.mark == '-' ? -1 : 1;
|
|
495
|
-
|
|
496
|
-
if (b.type.name !== a.type.name) {
|
|
497
|
-
if (b.type.name == 'VIEW')
|
|
498
|
-
return -1;
|
|
499
|
-
if (a.type.name == 'VIEW')
|
|
500
|
-
return 1;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
return a.path.localeCompare(b.path);
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
const typeMap = {
|
|
509
|
-
events: {
|
|
510
|
-
name: 'EVENT',
|
|
511
|
-
abbr: 'EVNT',
|
|
512
|
-
color: 'cyan'
|
|
513
|
-
},
|
|
514
|
-
functions: {
|
|
515
|
-
name: 'FUNCTION',
|
|
516
|
-
abbr: 'FUNC',
|
|
517
|
-
color: 'cyan'
|
|
518
|
-
},
|
|
519
|
-
procedures: {
|
|
520
|
-
name: 'PROCEDURE',
|
|
521
|
-
abbr: 'PROC',
|
|
522
|
-
color: 'yellow'
|
|
523
|
-
},
|
|
524
|
-
triggers: {
|
|
525
|
-
name: 'TRIGGER',
|
|
526
|
-
abbr: 'TRIG',
|
|
527
|
-
color: 'blue'
|
|
528
|
-
},
|
|
529
|
-
views: {
|
|
530
|
-
name: 'VIEW',
|
|
531
|
-
abbr: 'VIEW',
|
|
532
|
-
color: 'magenta'
|
|
533
|
-
},
|
|
534
|
-
};
|
|
535
|
-
|
|
536
|
-
const routineTypes = new Set([
|
|
537
|
-
'FUNCTION',
|
|
538
|
-
'PROCEDURE'
|
|
539
|
-
]);
|
|
540
|
-
|
|
541
|
-
class Routine {
|
|
542
|
-
constructor(change) {
|
|
543
|
-
const path = change.path;
|
|
544
|
-
const split = path.split('/');
|
|
545
|
-
|
|
546
|
-
const schema = split[0];
|
|
547
|
-
const type = typeMap[split[1]];
|
|
548
|
-
const name = split[2];
|
|
549
|
-
|
|
550
|
-
if (split.length !== 3 || !type)
|
|
551
|
-
throw new Error(`Wrong routine path for '${path}', check that the sql file is located in the correct directory`);
|
|
552
|
-
|
|
553
|
-
Object.assign(this, {
|
|
554
|
-
path,
|
|
555
|
-
mark: change.mark,
|
|
556
|
-
type,
|
|
557
|
-
schema,
|
|
558
|
-
name,
|
|
559
|
-
fullName: `${schema}.${name}`,
|
|
560
|
-
isRoutine: routineTypes.has(type.name)
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
module.exports = Push;
|
|
566
|
-
|
|
567
|
-
if (require.main === module)
|
|
568
|
-
new Myt().run(Push);
|
|
1
|
+
const Myt = require('./myt');
|
|
2
|
+
const Command = require('./lib/command');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const nodegit = require('nodegit');
|
|
5
|
+
const ExporterEngine = require('./lib/exporter-engine');
|
|
6
|
+
const connExt = require('./lib/conn');
|
|
7
|
+
const repoExt = require('./lib/repo');
|
|
8
|
+
const SqlString = require('sqlstring');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Pushes changes to remote.
|
|
12
|
+
*/
|
|
13
|
+
class Push extends Command {
|
|
14
|
+
static usage = {
|
|
15
|
+
description: 'Apply changes into database',
|
|
16
|
+
params: {
|
|
17
|
+
force: 'Answer yes to all questions',
|
|
18
|
+
commit: 'Wether to save the commit SHA into database',
|
|
19
|
+
sums: 'Save SHA sums of pushed objects',
|
|
20
|
+
triggers: 'Wether to exclude triggers, used to generate local DB'
|
|
21
|
+
},
|
|
22
|
+
operand: 'remote'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
static opts = {
|
|
26
|
+
alias: {
|
|
27
|
+
force: 'f',
|
|
28
|
+
commit: 'c',
|
|
29
|
+
sums: 's',
|
|
30
|
+
triggers: 't'
|
|
31
|
+
},
|
|
32
|
+
boolean: [
|
|
33
|
+
'force',
|
|
34
|
+
'commit',
|
|
35
|
+
'sums',
|
|
36
|
+
'triggers'
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
async run(myt, opts) {
|
|
41
|
+
const conn = await myt.dbConnect();
|
|
42
|
+
this.conn = conn;
|
|
43
|
+
|
|
44
|
+
if (opts.remote == 'local')
|
|
45
|
+
opts.commit = true;
|
|
46
|
+
|
|
47
|
+
// Obtain exclusive lock
|
|
48
|
+
|
|
49
|
+
const [[row]] = await conn.query(
|
|
50
|
+
`SELECT GET_LOCK('myt_push', 30) getLock`);
|
|
51
|
+
|
|
52
|
+
const pingTimeout = setInterval(async() => {
|
|
53
|
+
try {
|
|
54
|
+
await conn.ping()
|
|
55
|
+
} catch (e) {}
|
|
56
|
+
}, 60 * 1000);
|
|
57
|
+
|
|
58
|
+
if (!row.getLock) {
|
|
59
|
+
let isUsed = 0;
|
|
60
|
+
|
|
61
|
+
if (row.getLock == 0) {
|
|
62
|
+
const [[row]] = await conn.query(
|
|
63
|
+
`SELECT IS_USED_LOCK('myt_push') isUsed`);
|
|
64
|
+
isUsed = row.isUsed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error(`Cannot obtain exclusive lock, used by connection ${isUsed}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function releaseLock() {
|
|
71
|
+
await conn.query(`DO RELEASE_LOCK('myt_push')`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await this.push(myt, opts, conn);
|
|
76
|
+
} catch(err) {
|
|
77
|
+
try {
|
|
78
|
+
await releaseLock();
|
|
79
|
+
} catch (e) {}
|
|
80
|
+
throw err;
|
|
81
|
+
} finally {
|
|
82
|
+
clearInterval(pingTimeout);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await releaseLock();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async push(myt, opts, conn) {
|
|
89
|
+
const pushConn = await myt.createConnection();
|
|
90
|
+
|
|
91
|
+
// Get database version
|
|
92
|
+
|
|
93
|
+
const version = await myt.fetchDbVersion() || {};
|
|
94
|
+
|
|
95
|
+
console.log(
|
|
96
|
+
`Database information:`
|
|
97
|
+
+ `\n -> Version: ${version.number}`
|
|
98
|
+
+ `\n -> Commit: ${version.gitCommit}`
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (!version.number)
|
|
102
|
+
version.number = String('0').padStart(opts.versionDigits, '0');
|
|
103
|
+
if (!/^[0-9]*$/.test(version.number))
|
|
104
|
+
throw new Error('Wrong database version');
|
|
105
|
+
|
|
106
|
+
// Prevent push to production by mistake
|
|
107
|
+
|
|
108
|
+
if (opts.remote == 'production') {
|
|
109
|
+
console.log(
|
|
110
|
+
'\n ( ( ) ( ( ) ) '
|
|
111
|
+
+ '\n )\\ ))\\ ) ( /( )\\ ) ( ))\\ ) ( /( ( /( '
|
|
112
|
+
+ '\n(()/(()/( )\\()|()/( ( )\\ ) /(()/( )\\()) )\\())'
|
|
113
|
+
+ '\n /(_))(_)|(_)\\ /(_)) )\\ (((_) ( )(_))(_)|(_)\\ ((_)\\ '
|
|
114
|
+
+ '\n(_))(_)) ((_|_))_ _ ((_))\\___(_(_()|__)) ((_) _((_)'
|
|
115
|
+
+ '\n| _ \\ _ \\ / _ \\| \\| | | ((/ __|_ _|_ _| / _ \\| \\| |'
|
|
116
|
+
+ '\n| _/ /| (_) | |) | |_| || (__ | | | | | (_) | . |'
|
|
117
|
+
+ '\n|_| |_|_\\ \\___/|___/ \\___/ \\___| |_| |___| \\___/|_|\\_|'
|
|
118
|
+
+ '\n'
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (!opts.force) {
|
|
122
|
+
const readline = require('readline');
|
|
123
|
+
const rl = readline.createInterface({
|
|
124
|
+
input: process.stdin,
|
|
125
|
+
output: process.stdout
|
|
126
|
+
});
|
|
127
|
+
const answer = await new Promise(resolve => {
|
|
128
|
+
rl.question('Are you sure? (Default: no) [yes|no] ', resolve);
|
|
129
|
+
});
|
|
130
|
+
rl.close();
|
|
131
|
+
|
|
132
|
+
if (answer !== 'yes')
|
|
133
|
+
throw new Error('Changes aborted');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Apply versions
|
|
138
|
+
|
|
139
|
+
console.log('Applying versions.');
|
|
140
|
+
|
|
141
|
+
let nChanges = 0;
|
|
142
|
+
let silent = true;
|
|
143
|
+
const versionsDir = opts.versionsDir;
|
|
144
|
+
|
|
145
|
+
function logVersion(version, name, error) {
|
|
146
|
+
console.log('', version.bold, name);
|
|
147
|
+
}
|
|
148
|
+
function logScript(type, message, error) {
|
|
149
|
+
console.log(' ', type.bold, message);
|
|
150
|
+
}
|
|
151
|
+
function isUndoScript(script) {
|
|
152
|
+
return /\.undo\.sql$/.test(script);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const skipFiles = new Set([
|
|
156
|
+
'README.md',
|
|
157
|
+
'.archive'
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
if (await fs.pathExists(versionsDir)) {
|
|
161
|
+
const versionDirs = await fs.readdir(versionsDir);
|
|
162
|
+
|
|
163
|
+
for (const versionDir of versionDirs) {
|
|
164
|
+
if (skipFiles.has(versionDir)) continue;
|
|
165
|
+
|
|
166
|
+
const dirVersion = myt.parseVersionDir(versionDir);
|
|
167
|
+
if (!dirVersion) {
|
|
168
|
+
logVersion('[?????]'.yellow, versionDir,
|
|
169
|
+
`Wrong directory name.`
|
|
170
|
+
);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const versionNumber = dirVersion.number;
|
|
175
|
+
const versionName = dirVersion.name;
|
|
176
|
+
|
|
177
|
+
if (versionNumber.length != version.number.length) {
|
|
178
|
+
logVersion('[*****]'.gray, versionDir,
|
|
179
|
+
`Bad version length, should have ${version.number.length} characters.`
|
|
180
|
+
);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const scriptsDir = `${versionsDir}/${versionDir}`;
|
|
185
|
+
const scripts = await fs.readdir(scriptsDir);
|
|
186
|
+
|
|
187
|
+
const [versionLog] = await conn.query(
|
|
188
|
+
`SELECT file FROM versionLog
|
|
189
|
+
WHERE code = ?
|
|
190
|
+
AND number = ?
|
|
191
|
+
AND errorNumber IS NULL`,
|
|
192
|
+
[opts.code, versionNumber]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
for (const script of scripts)
|
|
196
|
+
if (!isUndoScript(script)
|
|
197
|
+
&& versionLog.findIndex(x => x.file == script) === -1) {
|
|
198
|
+
silent = false;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (silent) continue;
|
|
203
|
+
logVersion(`[${versionNumber}]`.cyan, versionName);
|
|
204
|
+
|
|
205
|
+
for (const script of scripts) {
|
|
206
|
+
if (!/^[0-9]{2}-[a-zA-Z0-9_]+(.undo)?\.sql$/.test(script)) {
|
|
207
|
+
logScript('[W]'.yellow, script, `Wrong file name.`);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (isUndoScript(script))
|
|
211
|
+
continue;
|
|
212
|
+
|
|
213
|
+
const [[row]] = await conn.query(
|
|
214
|
+
`SELECT errorNumber FROM versionLog
|
|
215
|
+
WHERE code = ?
|
|
216
|
+
AND number = ?
|
|
217
|
+
AND file = ?`,
|
|
218
|
+
[
|
|
219
|
+
opts.code,
|
|
220
|
+
versionNumber,
|
|
221
|
+
script
|
|
222
|
+
]
|
|
223
|
+
);
|
|
224
|
+
const apply = !row || row.errorNumber;
|
|
225
|
+
const actionMsg = apply ? '[+]'.green : '[I]'.blue;
|
|
226
|
+
|
|
227
|
+
logScript(actionMsg, script);
|
|
228
|
+
if (!apply) continue;
|
|
229
|
+
|
|
230
|
+
let err;
|
|
231
|
+
try {
|
|
232
|
+
await connExt.queryFromFile(pushConn,
|
|
233
|
+
`${scriptsDir}/${script}`);
|
|
234
|
+
} catch (e) {
|
|
235
|
+
err = e;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
await conn.query(
|
|
239
|
+
`INSERT INTO versionLog
|
|
240
|
+
SET code = ?,
|
|
241
|
+
number = ?,
|
|
242
|
+
file = ?,
|
|
243
|
+
user = USER(),
|
|
244
|
+
updated = NOW(),
|
|
245
|
+
errorNumber = ?,
|
|
246
|
+
errorMessage = ?
|
|
247
|
+
ON DUPLICATE KEY UPDATE
|
|
248
|
+
updated = VALUES(updated),
|
|
249
|
+
user = VALUES(user),
|
|
250
|
+
errorNumber = VALUES(errorNumber),
|
|
251
|
+
errorMessage = VALUES(errorMessage)`,
|
|
252
|
+
[
|
|
253
|
+
opts.code,
|
|
254
|
+
versionNumber,
|
|
255
|
+
script,
|
|
256
|
+
err && err.errno,
|
|
257
|
+
err && err.message
|
|
258
|
+
]
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (err) throw err;
|
|
262
|
+
nChanges++;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await this.updateVersion('number', versionNumber);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Apply routines
|
|
270
|
+
|
|
271
|
+
console.log('Applying changed routines.');
|
|
272
|
+
|
|
273
|
+
const gitExists = await fs.pathExists(`${opts.workspace}/.git`);
|
|
274
|
+
|
|
275
|
+
let nRoutines = 0;
|
|
276
|
+
const changes = await this.changedRoutines(version.gitCommit);
|
|
277
|
+
|
|
278
|
+
const routines = [];
|
|
279
|
+
for (const change of changes)
|
|
280
|
+
if (change.isRoutine)
|
|
281
|
+
routines.push([
|
|
282
|
+
change.schema,
|
|
283
|
+
change.name,
|
|
284
|
+
change.type.name
|
|
285
|
+
]);
|
|
286
|
+
|
|
287
|
+
if (routines.length) {
|
|
288
|
+
await conn.query(
|
|
289
|
+
`DROP TEMPORARY TABLE IF EXISTS tProcsPriv`
|
|
290
|
+
);
|
|
291
|
+
await conn.query(
|
|
292
|
+
`CREATE TEMPORARY TABLE tProcsPriv
|
|
293
|
+
ENGINE = MEMORY
|
|
294
|
+
SELECT * FROM mysql.procs_priv
|
|
295
|
+
WHERE (Db, Routine_name, Routine_type) IN (?)`,
|
|
296
|
+
[routines]
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const engine = new ExporterEngine(conn, opts);
|
|
301
|
+
await engine.init();
|
|
302
|
+
|
|
303
|
+
async function finalize() {
|
|
304
|
+
await engine.saveInfo();
|
|
305
|
+
|
|
306
|
+
if (routines.length) {
|
|
307
|
+
await conn.query('FLUSH PRIVILEGES');
|
|
308
|
+
await conn.query(`DROP TEMPORARY TABLE tProcsPriv`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
for (const change of changes)
|
|
313
|
+
try {
|
|
314
|
+
if (opts.trigger && change.type.name === 'TRIGGER')
|
|
315
|
+
continue;
|
|
316
|
+
|
|
317
|
+
const schema = change.schema;
|
|
318
|
+
const name = change.name;
|
|
319
|
+
const type = change.type.name.toLowerCase();
|
|
320
|
+
const fullPath = `${opts.routinesDir}/${change.path}.sql`;
|
|
321
|
+
const exists = await fs.pathExists(fullPath);
|
|
322
|
+
|
|
323
|
+
let newSql;
|
|
324
|
+
if (exists)
|
|
325
|
+
newSql = await fs.readFile(fullPath, 'utf8');
|
|
326
|
+
const oldSql = await engine.fetchRoutine(type, schema, name);
|
|
327
|
+
const oldSum = engine.getShaSum(type, schema, name);
|
|
328
|
+
const isEqual = newSql == oldSql;
|
|
329
|
+
|
|
330
|
+
let statusMsg;
|
|
331
|
+
if (exists && !oldSql)
|
|
332
|
+
statusMsg = '[+]'.green;
|
|
333
|
+
else if (!exists)
|
|
334
|
+
statusMsg = '[-]'.red;
|
|
335
|
+
else
|
|
336
|
+
statusMsg = '[·]'.yellow;
|
|
337
|
+
|
|
338
|
+
let actionMsg;
|
|
339
|
+
if (isEqual)
|
|
340
|
+
actionMsg = '[I]'.blue;
|
|
341
|
+
else
|
|
342
|
+
actionMsg = '[A]'.green;
|
|
343
|
+
|
|
344
|
+
const typeMsg = `[${change.type.abbr}]`[change.type.color];
|
|
345
|
+
console.log('',
|
|
346
|
+
(statusMsg + actionMsg).bold,
|
|
347
|
+
typeMsg.bold,
|
|
348
|
+
change.fullName
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
if (!isEqual) {
|
|
352
|
+
const scapedSchema = SqlString.escapeId(schema, true);
|
|
353
|
+
|
|
354
|
+
if (exists) {
|
|
355
|
+
if (change.type.name === 'VIEW')
|
|
356
|
+
await pushConn.query(`USE ${scapedSchema}`);
|
|
357
|
+
|
|
358
|
+
await connExt.multiQuery(pushConn, newSql);
|
|
359
|
+
|
|
360
|
+
if (change.isRoutine) {
|
|
361
|
+
await conn.query(
|
|
362
|
+
`INSERT IGNORE INTO mysql.procs_priv
|
|
363
|
+
SELECT * FROM tProcsPriv
|
|
364
|
+
WHERE Db = ?
|
|
365
|
+
AND Routine_name = ?
|
|
366
|
+
AND Routine_type = ?`,
|
|
367
|
+
[schema, name, change.type.name]
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (opts.sums || oldSum || (opts.sumViews && type === 'view'))
|
|
372
|
+
await engine.fetchShaSum(type, schema, name);
|
|
373
|
+
} else {
|
|
374
|
+
const escapedName =
|
|
375
|
+
scapedSchema + '.' +
|
|
376
|
+
SqlString.escapeId(name, true);
|
|
377
|
+
|
|
378
|
+
const query = `DROP ${change.type.name} IF EXISTS ${escapedName}`;
|
|
379
|
+
await pushConn.query(query);
|
|
380
|
+
|
|
381
|
+
engine.deleteShaSum(type, schema, name);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
nRoutines++;
|
|
385
|
+
}
|
|
386
|
+
} catch (err) {
|
|
387
|
+
try {
|
|
388
|
+
await finalize();
|
|
389
|
+
} catch (e) {
|
|
390
|
+
console.error(e);
|
|
391
|
+
}
|
|
392
|
+
throw err;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
await finalize();
|
|
396
|
+
|
|
397
|
+
if (nRoutines > 0) {
|
|
398
|
+
console.log(` -> ${nRoutines} routines have changed.`);
|
|
399
|
+
} else
|
|
400
|
+
console.log(` -> No routines changed.`);
|
|
401
|
+
|
|
402
|
+
if (gitExists && opts.commit) {
|
|
403
|
+
const repo = await nodegit.Repository.open(this.opts.workspace);
|
|
404
|
+
const head = await repo.getHeadCommit();
|
|
405
|
+
|
|
406
|
+
if (head && version.gitCommit !== head.sha())
|
|
407
|
+
await this.updateVersion('gitCommit', head.sha());
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// End
|
|
411
|
+
|
|
412
|
+
await pushConn.end();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async updateVersion(column, value) {
|
|
416
|
+
column = SqlString.escapeId(column, true);
|
|
417
|
+
await this.conn.query(
|
|
418
|
+
`INSERT INTO version
|
|
419
|
+
SET code = ?,
|
|
420
|
+
${column} = ?,
|
|
421
|
+
updated = NOW()
|
|
422
|
+
ON DUPLICATE KEY UPDATE
|
|
423
|
+
${column} = VALUES(${column}),
|
|
424
|
+
updated = VALUES(updated)`,
|
|
425
|
+
[
|
|
426
|
+
this.opts.code,
|
|
427
|
+
value
|
|
428
|
+
]
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async changedRoutines(commitSha) {
|
|
433
|
+
const repo = await this.myt.openRepo();
|
|
434
|
+
const changes = [];
|
|
435
|
+
const changesMap = new Map();
|
|
436
|
+
|
|
437
|
+
async function pushChanges(diff) {
|
|
438
|
+
if (!diff) return;
|
|
439
|
+
const patches = await diff.patches();
|
|
440
|
+
|
|
441
|
+
for (const patch of patches) {
|
|
442
|
+
const path = patch.newFile().path();
|
|
443
|
+
const match = path.match(/^routines\/(.+)\.sql$/);
|
|
444
|
+
if (!match) continue;
|
|
445
|
+
|
|
446
|
+
let change = changesMap.get(match[1]);
|
|
447
|
+
if (!change) {
|
|
448
|
+
change = {path: match[1]};
|
|
449
|
+
changes.push(change);
|
|
450
|
+
changesMap.set(match[1], change);
|
|
451
|
+
}
|
|
452
|
+
change.mark = patch.isDeleted() ? '-' : '+';
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const head = await repo.getHeadCommit();
|
|
457
|
+
|
|
458
|
+
if (head && commitSha) {
|
|
459
|
+
let commit;
|
|
460
|
+
let notFound;
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
commit = await repo.getCommit(commitSha);
|
|
464
|
+
notFound = false;
|
|
465
|
+
} catch (err) {
|
|
466
|
+
if (err.errorFunction == 'Commit.lookup')
|
|
467
|
+
notFound = true;
|
|
468
|
+
else
|
|
469
|
+
throw err;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (notFound) {
|
|
473
|
+
console.warn(`Database commit not found, trying git fetch`.yellow);
|
|
474
|
+
await repo.fetchAll();
|
|
475
|
+
commit = await repo.getCommit(commitSha);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const commitTree = await commit.getTree();
|
|
479
|
+
|
|
480
|
+
const headTree = await head.getTree();
|
|
481
|
+
const diff = await headTree.diff(commitTree);
|
|
482
|
+
await pushChanges(diff);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
await pushChanges(await repoExt.getUnstaged(repo));
|
|
486
|
+
await pushChanges(await repoExt.getStaged(repo));
|
|
487
|
+
|
|
488
|
+
const routines = [];
|
|
489
|
+
for (const change of changes)
|
|
490
|
+
routines.push(new Routine(change));
|
|
491
|
+
|
|
492
|
+
return routines.sort((a, b) => {
|
|
493
|
+
if (b.mark != a.mark)
|
|
494
|
+
return b.mark == '-' ? -1 : 1;
|
|
495
|
+
|
|
496
|
+
if (b.type.name !== a.type.name) {
|
|
497
|
+
if (b.type.name == 'VIEW')
|
|
498
|
+
return -1;
|
|
499
|
+
if (a.type.name == 'VIEW')
|
|
500
|
+
return 1;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return a.path.localeCompare(b.path);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const typeMap = {
|
|
509
|
+
events: {
|
|
510
|
+
name: 'EVENT',
|
|
511
|
+
abbr: 'EVNT',
|
|
512
|
+
color: 'cyan'
|
|
513
|
+
},
|
|
514
|
+
functions: {
|
|
515
|
+
name: 'FUNCTION',
|
|
516
|
+
abbr: 'FUNC',
|
|
517
|
+
color: 'cyan'
|
|
518
|
+
},
|
|
519
|
+
procedures: {
|
|
520
|
+
name: 'PROCEDURE',
|
|
521
|
+
abbr: 'PROC',
|
|
522
|
+
color: 'yellow'
|
|
523
|
+
},
|
|
524
|
+
triggers: {
|
|
525
|
+
name: 'TRIGGER',
|
|
526
|
+
abbr: 'TRIG',
|
|
527
|
+
color: 'blue'
|
|
528
|
+
},
|
|
529
|
+
views: {
|
|
530
|
+
name: 'VIEW',
|
|
531
|
+
abbr: 'VIEW',
|
|
532
|
+
color: 'magenta'
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
const routineTypes = new Set([
|
|
537
|
+
'FUNCTION',
|
|
538
|
+
'PROCEDURE'
|
|
539
|
+
]);
|
|
540
|
+
|
|
541
|
+
class Routine {
|
|
542
|
+
constructor(change) {
|
|
543
|
+
const path = change.path;
|
|
544
|
+
const split = path.split('/');
|
|
545
|
+
|
|
546
|
+
const schema = split[0];
|
|
547
|
+
const type = typeMap[split[1]];
|
|
548
|
+
const name = split[2];
|
|
549
|
+
|
|
550
|
+
if (split.length !== 3 || !type)
|
|
551
|
+
throw new Error(`Wrong routine path for '${path}', check that the sql file is located in the correct directory`);
|
|
552
|
+
|
|
553
|
+
Object.assign(this, {
|
|
554
|
+
path,
|
|
555
|
+
mark: change.mark,
|
|
556
|
+
type,
|
|
557
|
+
schema,
|
|
558
|
+
name,
|
|
559
|
+
fullName: `${schema}.${name}`,
|
|
560
|
+
isRoutine: routineTypes.has(type.name)
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
module.exports = Push;
|
|
566
|
+
|
|
567
|
+
if (require.main === module)
|
|
568
|
+
new Myt().run(Push);
|