@verdnatura/myt 1.5.6 → 1.5.8

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/README.md CHANGED
@@ -45,6 +45,8 @@ Database versioning commands:
45
45
  * **pull**: Incorporate database routine changes into workspace.
46
46
  * **push**: Apply changes into database.
47
47
  * **version**: Creates a new version.
48
+ * **create**: Creates a new routine file.
49
+ * **clean**: Cleans old versions.
48
50
 
49
51
  Local server management commands:
50
52
 
@@ -195,6 +197,14 @@ name mixing a color with a plant name.
195
197
  $ myt version [<name>]
196
198
  ```
197
199
 
200
+ ### create
201
+
202
+ Creates a new routine file with a default template.
203
+
204
+ ```text
205
+ $ myt create [-t <type>] <schema>.<name>
206
+ ```
207
+
198
208
  ### clean
199
209
 
200
210
  Cleans all already applied versions older than *maxOldVersions*.
@@ -1,12 +1,12 @@
1
1
  DROP EVENT IF EXISTS <%- schema %>.<%- name %>;
2
2
  DELIMITER $$
3
3
  CREATE DEFINER=<%- definer %> EVENT <%- schema %>.<%- name %><%
4
- if (type == 'RECURRING') { %>
4
+ if (locals.type == 'RECURRING') { %>
5
5
  ON SCHEDULE EVERY <%- intervalValue %> <%- intervalField %><%
6
- if (starts) { %>
6
+ if (locals.starts) { %>
7
7
  STARTS <%- starts %><%
8
8
  }
9
- if (ends) { %>
9
+ if (locals.ends) { %>
10
10
  ENDS <%- ends %><%
11
11
  }
12
12
  } else { %>
@@ -14,7 +14,7 @@ if (type == 'RECURRING') { %>
14
14
  } %>
15
15
  ON COMPLETION <%- onCompletion %>
16
16
  <%- status %><%
17
- if (comment) { %>
17
+ if (locals.comment) { %>
18
18
  COMMENT <%- comment %><%
19
19
  } %>
20
20
  DO <%- body %>$$
@@ -8,6 +8,12 @@ module.exports = {
8
8
  'executeAt',
9
9
  'comment'
10
10
  ],
11
+ defaults: {
12
+ type: 'RECURRING',
13
+ intervalValue: 1,
14
+ intervalField: 'DAY',
15
+ onCompletion: 'PRESERVE'
16
+ },
11
17
  formatter(params) {
12
18
  let status;
13
19
  switch(params.status){
@@ -1,19 +1,19 @@
1
1
  DROP FUNCTION IF EXISTS <%- schema %>.<%- name %>;
2
2
  DELIMITER $$
3
- CREATE DEFINER=<%- definer %> FUNCTION <%- schema %>.<%- name %>(<%- paramList %>)
3
+ CREATE DEFINER=<%- definer %> FUNCTION <%- schema %>.<%- name %>(<%- locals.paramList %>)
4
4
  RETURNS <%- returns %><%
5
- if (isDeterministic == 'NO') { %>
5
+ if (locals.isDeterministic == 'NO') { %>
6
6
  NOT DETERMINISTIC<%
7
7
  } else { %>
8
8
  DETERMINISTIC<%
9
9
  }
10
- if (dataAccess) { %>
10
+ if (locals.dataAccess) { %>
11
11
  <%- dataAccess %><%
12
12
  }
13
- if (securityType == 'INVOKER') { %>
13
+ if (locals.securityType == 'INVOKER') { %>
14
14
  SQL SECURITY <%- securityType %><%
15
15
  }
16
- if (comment) { %>
16
+ if (locals.comment) { %>
17
17
  COMMENT <%- comment %><%
18
18
  } %>
19
19
  <%- body %>$$
@@ -5,6 +5,10 @@ module.exports = {
5
5
  escapeCols: [
6
6
  'comment'
7
7
  ],
8
+ defaults: {
9
+ returns: 'INT',
10
+ isDeterministic: 'NO'
11
+ },
8
12
  formatter(params) {
9
13
  let dataAccess;
10
14
  switch(params.dataAccess) {
@@ -1,13 +1,13 @@
1
1
  DROP PROCEDURE IF EXISTS <%- schema %>.<%- name %>;
2
2
  DELIMITER $$
3
- CREATE DEFINER=<%- definer %> PROCEDURE <%- schema %>.<%- name %>(<%- paramList %>)<%
4
- if (dataAccess) { %>
3
+ CREATE DEFINER=<%- definer %> PROCEDURE <%- schema %>.<%- name %>(<%- locals.paramList %>)<%
4
+ if (locals.dataAccess) { %>
5
5
  <%- dataAccess %><%
6
6
  }
7
- if (securityType == 'INVOKER') { %>
7
+ if (locals.securityType == 'INVOKER') { %>
8
8
  SQL SECURITY <%- securityType %><%
9
9
  }
10
- if (comment) { %>
10
+ if (locals.comment) { %>
11
11
  COMMENT <%- comment %><%
12
12
  } %>
13
13
  <%- body %>$$
@@ -1,5 +1,10 @@
1
1
 
2
2
  module.exports = {
3
3
  schemaCol: 'TRIGGER_SCHEMA',
4
- nameCol: 'TRIGGER_NAME'
4
+ nameCol: 'TRIGGER_NAME',
5
+ defaults: {
6
+ actionTiming: 'AFTER',
7
+ actionType: 'INSERT',
8
+ table: 'table'
9
+ }
5
10
  };
@@ -2,6 +2,6 @@ CREATE OR REPLACE DEFINER=<%- definer %>
2
2
  SQL SECURITY <%- securityType %>
3
3
  VIEW <%- schema %>.<%- name %>
4
4
  AS <%- definition %><%
5
- if (checkOption != 'NONE') { %>
5
+ if (locals.checkOption != 'NONE') { %>
6
6
  WITH <%- checkOption %> CHECK OPTION<%
7
7
  } %>
package/exporters/view.js CHANGED
@@ -4,6 +4,10 @@ const sqlFormatter = require('@sqltools/formatter');
4
4
  module.exports = {
5
5
  schemaCol: 'TABLE_SCHEMA',
6
6
  nameCol: 'TABLE_NAME',
7
+ defaults: {
8
+ securityType: 'DEFINER',
9
+ checkOption: 'NONE'
10
+ },
7
11
  formatter(params) {
8
12
  params.definition = sqlFormatter.format(params.definition, {
9
13
  indent: '\t',
package/lib/command.js CHANGED
@@ -2,12 +2,9 @@
2
2
  * Base class for Myt commands.
3
3
  */
4
4
  module.exports = class MytCommand {
5
- get usage() {
6
- return {};
7
- }
8
-
9
- get localOpts() {
10
- return {};
5
+ constructor(myt, opts) {
6
+ this.myt = myt;
7
+ this.opts = opts;
11
8
  }
12
9
 
13
10
  async run(myt, opts) {
package/lib/conn.js ADDED
@@ -0,0 +1,141 @@
1
+ const fs = require('fs-extra');
2
+
3
+ /**
4
+ * Executes an SQL script.
5
+ *
6
+ * @param {Connection} conn MySQL connection object
7
+ * @returns {Array<Result>} The resultset
8
+ */
9
+ async function queryFromFile(conn, file) {
10
+ const sql = await fs.readFile(file, 'utf8');
11
+ return await this.multiQuery(conn, sql);
12
+ }
13
+
14
+ /**
15
+ * Executes a multi-query string.
16
+ *
17
+ * @param {Connection} conn MySQL connection object
18
+ * @param {String} sql SQL multi-query string
19
+ * @returns {Array<Result>} The resultset
20
+ */
21
+ async function multiQuery(conn, sql) {
22
+ let results = [];
23
+ const stmts = this.querySplit(sql);
24
+
25
+ for (const stmt of stmts)
26
+ results = results.concat(await conn.query(stmt));
27
+
28
+ return results;
29
+ }
30
+
31
+ /**
32
+ * Splits an SQL muti-query into a single-query array, it does an small
33
+ * parse to correctly handle the DELIMITER statement.
34
+ *
35
+ * @param {Array<String>} stmts The splitted SQL statements
36
+ */
37
+ function querySplit(sql) {
38
+ const stmts = [];
39
+ let i,
40
+ char,
41
+ token,
42
+ escaped,
43
+ stmtStart;
44
+
45
+ let delimiter = ';';
46
+ const delimiterRe = /\s*delimiter\s+(\S+)[^\S\r\n]*(?:\r?\n|\r|$)/yi;
47
+
48
+ function begins(str) {
49
+ let j;
50
+ for (j = 0; j < str.length; j++)
51
+ if (sql[i + j] != str[j])
52
+ return false;
53
+ i += j;
54
+ return true;
55
+ }
56
+
57
+ for (i = 0; i < sql.length;) {
58
+ stmtStart = i;
59
+
60
+ delimiterRe.lastIndex = i;
61
+ const match = sql.match(delimiterRe);
62
+ if (match) {
63
+ delimiter = match[1];
64
+ i += match[0].length;
65
+ continue;
66
+ }
67
+
68
+ let delimiterFound = false;
69
+ while (i < sql.length) {
70
+ char = sql[i];
71
+
72
+ if (token) {
73
+ if (!escaped && begins(token.end))
74
+ token = null;
75
+ else {
76
+ escaped = !escaped && token.escape(char);
77
+ i++;
78
+ }
79
+ } else {
80
+ delimiterFound = begins(delimiter);
81
+ if (delimiterFound) break;
82
+
83
+ const tok = tokenIndex.get(char);
84
+ if (tok && begins(tok.start))
85
+ token = tok;
86
+ else
87
+ i++;
88
+ }
89
+ }
90
+
91
+ let len = i - stmtStart;
92
+ if (delimiterFound) len -= delimiter.length;
93
+ const stmt = sql.substr(stmtStart, len);
94
+
95
+ if (!/^\s*$/.test(stmt))
96
+ stmts.push(stmt);
97
+ }
98
+
99
+ return stmts;
100
+ }
101
+
102
+ const tokens = {
103
+ string: {
104
+ start: '\'',
105
+ end: '\'',
106
+ escape: char => char == '\'' || char == '\\'
107
+ },
108
+ quotedString: {
109
+ start: '"',
110
+ end: '"',
111
+ escape: char => char == '"' || char == '\\'
112
+ },
113
+ id: {
114
+ start: '`',
115
+ end: '`',
116
+ escape: char => char == '`'
117
+ },
118
+ multiComment: {
119
+ start: '/*',
120
+ end: '*/',
121
+ escape: () => false
122
+ },
123
+ singleComment: {
124
+ start: '-- ',
125
+ end: '\n',
126
+ escape: () => false
127
+ }
128
+ };
129
+
130
+ const tokenIndex = new Map();
131
+ for (const tokenId in tokens) {
132
+ const token = tokens[tokenId];
133
+ tokenIndex.set(token.start[0], token);
134
+ }
135
+
136
+ module.exports = {
137
+ queryFromFile,
138
+ multiQuery,
139
+ querySplit,
140
+ tokens
141
+ };
package/lib/dumper.js ADDED
@@ -0,0 +1,83 @@
1
+ const docker = require('./docker');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const SqlString = require('sqlstring');
5
+
6
+ module.exports = class Dumper {
7
+ constructor(opts) {
8
+ this.opts = opts;
9
+ }
10
+
11
+ async init(dumpFile) {
12
+ const dumpDir = this.opts.dumpDir;
13
+ if (!await fs.pathExists(dumpDir))
14
+ await fs.mkdir(dumpDir);
15
+
16
+ const dumpPath = path.join(dumpDir, dumpFile);
17
+
18
+ // FIXME: If it's called after docker.build() statement it creates an
19
+ // "invalid" WriteStream
20
+ const dumpStream = await fs.createWriteStream(dumpPath);
21
+
22
+ const buidDir = path.join(__dirname, '..',)
23
+ await docker.build(buidDir, {
24
+ tag: 'myt/client',
25
+ file: path.join(buidDir, 'server', 'Dockerfile.client')
26
+ }, this.opts.debug);
27
+
28
+ this.dumpStream = dumpStream;
29
+ }
30
+
31
+ async use(schema) {
32
+ await this.dumpStream.write(
33
+ `USE ${SqlString.escapeId(schema, true)};\n`,
34
+ 'utf8'
35
+ );
36
+ }
37
+
38
+ async dumpFixtures(tables, replace) {
39
+ const fixturesArgs = [
40
+ '--no-create-info',
41
+ '--skip-triggers',
42
+ '--skip-extended-insert',
43
+ '--skip-disable-keys',
44
+ '--skip-add-locks',
45
+ '--skip-set-charset',
46
+ '--skip-comments',
47
+ '--skip-tz-utc'
48
+ ];
49
+
50
+ if (replace)
51
+ fixturesArgs.push('--replace');
52
+
53
+ for (const schema in tables) {
54
+ await this.use(schema);
55
+ const args = fixturesArgs.concat([schema], tables[schema]);
56
+ await this.runDump('mysqldump', args, this.dumpStream);
57
+ }
58
+ }
59
+
60
+ async runDump(command, args) {
61
+ const iniPath = path.join(this.opts.subdir || '', 'remotes', this.opts.iniFile);
62
+ const myArgs = [
63
+ `--defaults-file=${iniPath}`
64
+ ];
65
+ const execOptions = {
66
+ stdio: [
67
+ process.stdin,
68
+ this.dumpStream,
69
+ process.stderr
70
+ ]
71
+ };
72
+ const commandArgs = [command].concat(myArgs, args);
73
+ await docker.run('myt/client', commandArgs, {
74
+ addHost: 'host.docker.internal:host-gateway',
75
+ volume: `${this.opts.mytDir}:/workspace`,
76
+ rm: true
77
+ }, execOptions);
78
+ }
79
+
80
+ async end() {
81
+ await this.dumpStream.end();
82
+ }
83
+ }
@@ -1,4 +1,3 @@
1
-
2
1
  const shajs = require('sha.js');
3
2
  const fs = require('fs-extra');
4
3
  const Exporter = require('./exporter');
@@ -36,7 +35,7 @@ module.exports = class ExporterEngine {
36
35
  ];
37
36
 
38
37
  for (const type of types) {
39
- const exporter = new Exporter(this, type, this.conn);
38
+ const exporter = new Exporter(type);
40
39
  await exporter.init();
41
40
 
42
41
  this.exporters.push(exporter);
@@ -46,7 +45,7 @@ module.exports = class ExporterEngine {
46
45
 
47
46
  async fetchRoutine(type, schema, name) {
48
47
  const exporter = this.exporterMap[type];
49
- const [row] = await exporter.query(schema, name);
48
+ const [row] = await exporter.query(this.conn, schema, name);
50
49
  return row && exporter.format(row);
51
50
  }
52
51
 
package/lib/exporter.js CHANGED
@@ -1,13 +1,10 @@
1
-
2
1
  const ejs = require('ejs');
3
2
  const fs = require('fs-extra');
3
+ const SqlString = require('sqlstring');
4
4
 
5
5
  module.exports = class Exporter {
6
- constructor(engine, objectType, conn) {
7
- this.engine = engine;
6
+ constructor(objectType) {
8
7
  this.objectType = objectType;
9
- this.dstDir = `${objectType}s`;
10
- this.conn = conn;
11
8
  }
12
9
 
13
10
  async init() {
@@ -19,60 +16,7 @@ module.exports = class Exporter {
19
16
  this.attrs = require(`${templateDir}.js`);
20
17
  }
21
18
 
22
- async export(exportDir, schema, update, saveSum) {
23
- const res = await this.query(schema);
24
- if (!res.length) return;
25
-
26
- const routineDir = `${exportDir}/${schema}/${this.dstDir}`;
27
- if (!await fs.pathExists(routineDir))
28
- await fs.mkdir(routineDir);
29
-
30
- const routineSet = new Set();
31
- for (const params of res)
32
- routineSet.add(params.name);
33
-
34
- const routines = await fs.readdir(routineDir);
35
- for (const routineFile of routines) {
36
- const match = routineFile.match(/^(.*)\.sql$/);
37
- if (!match) continue;
38
- const routine = match[1];
39
- if (!routineSet.has(routine))
40
- await fs.remove(`${routineDir}/${routine}.sql`);
41
- }
42
-
43
- const engine = this.engine;
44
-
45
- for (const params of res) {
46
- const routineName = params.name;
47
- const sql = this.format(params);
48
- const routineFile = `${routineDir}/${routineName}.sql`;
49
-
50
- const oldSum = engine.getShaSum(routineName);
51
- if (oldSum || saveSum) {
52
- const shaSum = engine.shaSum(sql);
53
- if (oldSum !== shaSum) {
54
- engine.setShaSum(
55
- this.objectType, schema, routineName, shaSum);
56
- update = true;
57
- }
58
- } else if (params.modified && engine.lastPull) {
59
- if (params.modified > engine.lastPull)
60
- update = true;
61
- } else if (await fs.pathExists(routineFile)) {
62
- const currentSql = await fs.readFile(routineFile, 'utf8');
63
- if (sql != currentSql)
64
- update = true;
65
- } else
66
- update = true;
67
-
68
- if (update)
69
- await fs.writeFile(routineFile, sql);
70
- }
71
- }
72
-
73
- async query(schema, name) {
74
- const {conn} = this;
75
-
19
+ async query(conn, schema, name) {
76
20
  const ops = [];
77
21
  function addOp(col, value) {
78
22
  ops.push(conn.format('?? = ?', [col, value]));
@@ -93,22 +37,24 @@ module.exports = class Exporter {
93
37
  }
94
38
 
95
39
  format(params) {
96
- const {conn, attrs} = this;
40
+ const {attrs} = this;
41
+ params = Object.assign({}, attrs.defaults, params);
97
42
 
98
43
  if (attrs.formatter)
99
- attrs.formatter(params, conn);
44
+ attrs.formatter(params);
100
45
 
101
46
  if (attrs.escapeCols)
102
47
  for (const escapeCol of attrs.escapeCols) {
103
48
  if (params[escapeCol])
104
- params[escapeCol] = conn.escape(params[escapeCol])
49
+ params[escapeCol] = SqlString.escape(params[escapeCol])
105
50
  }
106
51
 
107
52
  const split = params.definer.split('@');
108
- params.schema = conn.escapeId(params.schema, true);
109
- params.name = conn.escapeId(params.name, true);
53
+ params.schema = SqlString.escapeId(params.schema, true);
54
+ params.name = SqlString.escapeId(params.name, true);
110
55
  params.definer =
111
- `${conn.escapeId(split[0], true)}@${conn.escapeId(split[1], true)}`;
56
+ SqlString.escapeId(split[0], true) + '@' +
57
+ SqlString.escapeId(split[1], true);
112
58
 
113
59
  return this.template(params);
114
60
  }
package/lib/repo.js ADDED
@@ -0,0 +1,29 @@
1
+ const nodegit = require('nodegit');
2
+
3
+ async function getStaged(repo) {
4
+ const head = await repo.getHeadCommit();
5
+
6
+ try {
7
+ const emptyTree = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
8
+ const headTree = await (head
9
+ ? head.getTree()
10
+ : nodegit.Tree.lookup(repo, emptyTree)
11
+ );
12
+ return await nodegit.Diff.treeToIndex(repo, headTree, null);
13
+ } catch (err) {
14
+ console.warn('Cannot fetch staged changes:', err.message);
15
+ }
16
+ }
17
+
18
+ async function getUnstaged(repo) {
19
+ const Diff = nodegit.Diff;
20
+ return await Diff.indexToWorkdir(repo, null, {
21
+ flags: Diff.OPTION.SHOW_UNTRACKED_CONTENT
22
+ | Diff.OPTION.RECURSE_UNTRACKED_DIRS
23
+ });
24
+ }
25
+
26
+ module.exports = {
27
+ getStaged,
28
+ getUnstaged
29
+ };
package/lib/util.js CHANGED
@@ -1,6 +1,7 @@
1
-
2
1
  function camelToSnake(str) {
3
2
  return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
4
3
  }
5
4
 
6
- module.exports.camelToSnake = camelToSnake;
5
+ module.exports = {
6
+ camelToSnake
7
+ };
package/myt-clean.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
4
3
  const fs = require('fs-extra');
@@ -11,7 +10,7 @@ class Clean extends Command {
11
10
  description: 'Cleans old applied versions'
12
11
  };
13
12
 
14
- static localOpts = {
13
+ static opts = {
15
14
  default: {
16
15
  remote: 'production'
17
16
  }
package/myt-create.js ADDED
@@ -0,0 +1,74 @@
1
+ const Myt = require('./myt');
2
+ const Command = require('./lib/command');
3
+ const Exporter = require('./lib/exporter');
4
+ const fs = require('fs-extra');
5
+
6
+ class Create extends Command {
7
+ static usage = {
8
+ description: 'Creates a new DB object',
9
+ params: {
10
+ type: 'The object type',
11
+ name: 'The object name, including schema'
12
+ },
13
+ operand: 'name'
14
+ };
15
+
16
+ static opts = {
17
+ alias: {
18
+ type: 't',
19
+ name: 'n'
20
+ },
21
+ string: [
22
+ 'type',
23
+ 'name'
24
+ ],
25
+ default: {
26
+ type: 'procedure'
27
+ }
28
+ };
29
+
30
+ async run(myt, opts) {
31
+ const match = opts.name.match(/^(\w+)\.(\w+)$/);
32
+ if (!match)
33
+ throw new Error('Invalid object name, should contain schema and routine name');
34
+
35
+ const schema = match[1];
36
+ const name = match[2];
37
+
38
+ const params = {
39
+ schema,
40
+ name,
41
+ definer: 'root@localhost'
42
+ };
43
+
44
+ switch (opts.type) {
45
+ case 'event':
46
+ case 'function':
47
+ case 'procedure':
48
+ case 'trigger':
49
+ params.body = "BEGIN\n-- Your code goes here\nEND";
50
+ break;
51
+ case 'view':
52
+ params.definition = "SELECT TRUE"
53
+ break;
54
+ }
55
+
56
+ const exporter = new Exporter(opts.type);
57
+ await exporter.init();
58
+ const sql = exporter.format(params);
59
+
60
+ const routineDir = `${opts.routinesDir}/${schema}/${opts.type}s`;
61
+ if (!await fs.pathExists(routineDir))
62
+ await fs.mkdir(routineDir);
63
+
64
+ const routineFile = `${routineDir}/${name}.sql`;
65
+ await fs.writeFile(routineFile, sql);
66
+
67
+ console.log('Routine created.');
68
+ }
69
+ }
70
+
71
+ module.exports = Create;
72
+
73
+ if (require.main === module)
74
+ new Myt().run(Create);