dbgate-api 4.3.4 → 4.4.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dbgate-api",
3
3
  "main": "src/index.js",
4
- "version": "4.3.4",
4
+ "version": "4.4.1",
5
5
  "homepage": "https://dbgate.org/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -26,9 +26,11 @@
26
26
  "compare-versions": "^3.6.0",
27
27
  "cors": "^2.8.5",
28
28
  "cross-env": "^6.0.3",
29
- "dbgate-query-splitter": "^4.3.4",
30
- "dbgate-sqltree": "^4.3.4",
31
- "dbgate-tools": "^4.3.4",
29
+ "dbgate-query-splitter": "^4.4.1",
30
+ "dbgate-sqltree": "^4.4.1",
31
+ "dbgate-tools": "^4.4.1",
32
+ "diff": "^5.0.0",
33
+ "diff2html": "^3.4.13",
32
34
  "eslint": "^6.8.0",
33
35
  "express": "^4.17.1",
34
36
  "express-basic-auth": "^1.2.0",
@@ -37,6 +39,7 @@
37
39
  "fs-reverse": "^0.0.3",
38
40
  "get-port": "^5.1.1",
39
41
  "http": "^0.0.0",
42
+ "js-yaml": "^4.1.0",
40
43
  "json-stable-stringify": "^1.0.1",
41
44
  "line-reader": "^0.4.0",
42
45
  "lodash": "^4.17.21",
@@ -62,7 +65,7 @@
62
65
  "devDependencies": {
63
66
  "@types/fs-extra": "^9.0.11",
64
67
  "@types/lodash": "^4.14.149",
65
- "dbgate-types": "^4.3.4",
68
+ "dbgate-types": "^4.4.1",
66
69
  "env-cmd": "^10.1.0",
67
70
  "node-loader": "^1.0.2",
68
71
  "nodemon": "^2.0.2",
@@ -3,10 +3,11 @@ const stream = require('stream');
3
3
  const readline = require('readline');
4
4
  const path = require('path');
5
5
  const { formatWithOptions } = require('util');
6
- const { archivedir } = require('../utility/directories');
6
+ const { archivedir, clearArchiveLinksCache, resolveArchiveFolder } = require('../utility/directories');
7
7
  const socket = require('../utility/socket');
8
8
  const JsonLinesDatastore = require('../utility/JsonLinesDatastore');
9
9
  const { saveFreeTableData } = require('../utility/freeTableStorage');
10
+ const loadFilesRecursive = require('../utility/loadFilesRecursive');
10
11
 
11
12
  module.exports = {
12
13
  folders_meta: 'get',
@@ -33,17 +34,40 @@ module.exports = {
33
34
  return true;
34
35
  },
35
36
 
37
+ createLink_meta: 'post',
38
+ async createLink({ linkedFolder }) {
39
+ const folder = await this.getNewArchiveFolder({ database: path.parse(linkedFolder).name + '.link' });
40
+ fs.writeFile(path.join(archivedir(), folder), linkedFolder);
41
+ clearArchiveLinksCache();
42
+ socket.emitChanged('archive-folders-changed');
43
+ return folder;
44
+ },
45
+
36
46
  files_meta: 'get',
37
47
  async files({ folder }) {
38
- const dir = path.join(archivedir(), folder);
48
+ const dir = resolveArchiveFolder(folder);
39
49
  if (!(await fs.exists(dir))) return [];
40
- const files = await fs.readdir(dir);
41
- return files
42
- .filter(name => name.endsWith('.jsonl'))
43
- .map(name => ({
44
- name: name.slice(0, -'.jsonl'.length),
45
- type: 'jsonl',
46
- }));
50
+ const files = await loadFilesRecursive(dir); // fs.readdir(dir);
51
+
52
+ function fileType(ext, type) {
53
+ return files
54
+ .filter(name => name.endsWith(ext))
55
+ .map(name => ({
56
+ name: name.slice(0, -ext.length),
57
+ label: path.parse(name.slice(0, -ext.length)).base,
58
+ type,
59
+ }));
60
+ }
61
+
62
+ return [
63
+ ...fileType('.jsonl', 'jsonl'),
64
+ ...fileType('.table.yaml', 'table.yaml'),
65
+ ...fileType('.view.sql', 'view.sql'),
66
+ ...fileType('.proc.sql', 'proc.sql'),
67
+ ...fileType('.func.sql', 'func.sql'),
68
+ ...fileType('.trigger.sql', 'trigger.sql'),
69
+ ...fileType('.matview.sql', 'matview.sql'),
70
+ ];
47
71
  },
48
72
 
49
73
  refreshFiles_meta: 'post',
@@ -57,28 +81,47 @@ module.exports = {
57
81
  },
58
82
 
59
83
  deleteFile_meta: 'post',
60
- async deleteFile({ folder, file }) {
61
- await fs.unlink(path.join(archivedir(), folder, `${file}.jsonl`));
84
+ async deleteFile({ folder, file, fileType }) {
85
+ await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`));
62
86
  socket.emitChanged(`archive-files-changed-${folder}`);
63
87
  },
64
88
 
89
+ renameFile_meta: 'post',
90
+ async renameFile({ folder, file, newFile, fileType }) {
91
+ await fs.rename(
92
+ path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
93
+ path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`)
94
+ );
95
+ socket.emitChanged(`archive-files-changed-${folder}`);
96
+ },
97
+
98
+ renameFolder_meta: 'post',
99
+ async renameFolder({ folder, newFolder }) {
100
+ await fs.rename(path.join(resolveArchiveFolder(folder)), path.join(resolveArchiveFolder(newFolder)));
101
+ socket.emitChanged(`archive-folders-changed`);
102
+ },
103
+
65
104
  deleteFolder_meta: 'post',
66
105
  async deleteFolder({ folder }) {
67
106
  if (!folder) throw new Error('Missing folder parameter');
68
- await fs.rmdir(path.join(archivedir(), folder), { recursive: true });
107
+ if (folder.endsWith('.link')) {
108
+ await fs.unlink(path.join(archivedir(), folder));
109
+ } else {
110
+ await fs.rmdir(path.join(archivedir(), folder), { recursive: true });
111
+ }
69
112
  socket.emitChanged(`archive-folders-changed`);
70
113
  },
71
114
 
72
115
  saveFreeTable_meta: 'post',
73
116
  async saveFreeTable({ folder, file, data }) {
74
- saveFreeTableData(path.join(archivedir(), folder, `${file}.jsonl`), data);
117
+ saveFreeTableData(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), data);
75
118
  return true;
76
119
  },
77
120
 
78
121
  loadFreeTable_meta: 'post',
79
122
  async loadFreeTable({ folder, file }) {
80
123
  return new Promise((resolve, reject) => {
81
- const fileStream = fs.createReadStream(path.join(archivedir(), folder, `${file}.jsonl`));
124
+ const fileStream = fs.createReadStream(path.join(resolveArchiveFolder(folder), `${file}.jsonl`));
82
125
  const liner = readline.createInterface({
83
126
  input: fileStream,
84
127
  });
@@ -95,4 +138,13 @@ module.exports = {
95
138
  });
96
139
  });
97
140
  },
141
+
142
+ async getNewArchiveFolder({ database }) {
143
+ if (!(await fs.exists(path.join(archivedir(), database)))) return database;
144
+ let index = 2;
145
+ while (await fs.exists(path.join(archivedir(), `${database}${index}`))) {
146
+ index += 1;
147
+ }
148
+ return `${database}${index}`;
149
+ },
98
150
  };
@@ -2,8 +2,9 @@ const path = require('path');
2
2
  const { fork } = require('child_process');
3
3
  const _ = require('lodash');
4
4
  const nedb = require('nedb-promises');
5
+ const fs = require('fs-extra');
5
6
 
6
- const { datadir } = require('../utility/directories');
7
+ const { datadir, filesdir } = require('../utility/directories');
7
8
  const socket = require('../utility/socket');
8
9
  const { encryptConnection } = require('../utility/crypting');
9
10
  const { handleProcessCommunication } = require('../utility/processComm');
@@ -175,4 +176,20 @@ module.exports = {
175
176
  const res = await this.datastore.find({ _id: conid });
176
177
  return res[0];
177
178
  },
179
+
180
+ newSqliteDatabase_meta: 'post',
181
+ async newSqliteDatabase({ file }) {
182
+ const sqliteDir = path.join(filesdir(), 'sqlite');
183
+ if (!(await fs.exists(sqliteDir))) {
184
+ await fs.mkdir(sqliteDir);
185
+ }
186
+ const databaseFile = path.join(sqliteDir, `${file}.sqlite`);
187
+ const res = await this.save({
188
+ engine: 'sqlite@dbgate-plugin-sqlite',
189
+ databaseFile,
190
+ singleDatabase: true,
191
+ defaultDatabase: `${file}.sqlite`,
192
+ });
193
+ return res;
194
+ },
178
195
  };
@@ -1,10 +1,30 @@
1
1
  const uuidv1 = require('uuid/v1');
2
2
  const connections = require('./connections');
3
+ const archive = require('./archive');
3
4
  const socket = require('../utility/socket');
4
5
  const { fork } = require('child_process');
5
- const { DatabaseAnalyser } = require('dbgate-tools');
6
+ const {
7
+ DatabaseAnalyser,
8
+ computeDbDiffRows,
9
+ getCreateObjectScript,
10
+ getAlterDatabaseScript,
11
+ generateDbPairingId,
12
+ matchPairedObjects,
13
+ extendDatabaseInfo,
14
+ modelCompareDbDiffOptions,
15
+ } = require('dbgate-tools');
16
+ const { html, parse } = require('diff2html');
6
17
  const { handleProcessCommunication } = require('../utility/processComm');
7
18
  const config = require('./config');
19
+ const fs = require('fs-extra');
20
+ const exportDbModel = require('../utility/exportDbModel');
21
+ const { archivedir, resolveArchiveFolder, uploadsdir } = require('../utility/directories');
22
+ const path = require('path');
23
+ const importDbModel = require('../utility/importDbModel');
24
+ const requireEngineDriver = require('../utility/requireEngineDriver');
25
+ const generateDeploySql = require('../shell/generateDeploySql');
26
+ const { createTwoFilesPatch } = require('diff');
27
+ const diff2htmlPage = require('../utility/diff2htmlPage');
8
28
 
9
29
  module.exports = {
10
30
  /** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
@@ -212,6 +232,11 @@ module.exports = {
212
232
 
213
233
  structure_meta: 'get',
214
234
  async structure({ conid, database }) {
235
+ if (conid == '__model') {
236
+ const model = await importDbModel(database);
237
+ return model;
238
+ }
239
+
215
240
  const opened = await this.ensureOpened(conid, database);
216
241
  return opened.structure;
217
242
  // const existing = this.opened.find((x) => x.conid == conid && x.database == database);
@@ -238,6 +263,38 @@ module.exports = {
238
263
  return res;
239
264
  },
240
265
 
266
+ exportModel_meta: 'post',
267
+ async exportModel({ conid, database }) {
268
+ const archiveFolder = await archive.getNewArchiveFolder({ database });
269
+ await fs.mkdir(path.join(archivedir(), archiveFolder));
270
+ const model = await this.structure({ conid, database });
271
+ await exportDbModel(model, path.join(archivedir(), archiveFolder));
272
+ socket.emitChanged(`archive-folders-changed`);
273
+ return { archiveFolder };
274
+ },
275
+
276
+ generateDeploySql_meta: 'post',
277
+ async generateDeploySql({ conid, database, archiveFolder }) {
278
+ const connection = await connections.get({ conid });
279
+ return generateDeploySql({
280
+ connection,
281
+ analysedStructure: await this.structure({ conid, database }),
282
+ modelFolder: resolveArchiveFolder(archiveFolder),
283
+ });
284
+ // const deployedModel = generateDbPairingId(await importDbModel(path.join(archivedir(), archiveFolder)));
285
+ // const currentModel = generateDbPairingId(await this.structure({ conid, database }));
286
+ // const currentModelPaired = matchPairedObjects(deployedModel, currentModel);
287
+ // const connection = await connections.get({ conid });
288
+ // const driver = requireEngineDriver(connection);
289
+ // const { sql } = getAlterDatabaseScript(currentModelPaired, deployedModel, {}, deployedModel, driver);
290
+ // return {
291
+ // deployedModel,
292
+ // currentModel,
293
+ // currentModelPaired,
294
+ // sql,
295
+ // };
296
+ // return sql;
297
+ },
241
298
  // runCommand_meta: 'post',
242
299
  // async runCommand({ conid, database, sql }) {
243
300
  // console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`);
@@ -245,4 +302,52 @@ module.exports = {
245
302
  // const res = await this.sendRequest(opened, { msgtype: 'queryData', sql });
246
303
  // return res;
247
304
  // },
305
+
306
+ async getUnifiedDiff({ sourceConid, sourceDatabase, targetConid, targetDatabase }) {
307
+ const dbDiffOptions = sourceConid == '__model' ? modelCompareDbDiffOptions : {};
308
+
309
+ const sourceDb = generateDbPairingId(
310
+ extendDatabaseInfo(await this.structure({ conid: sourceConid, database: sourceDatabase }))
311
+ );
312
+ const targetDb = generateDbPairingId(
313
+ extendDatabaseInfo(await this.structure({ conid: targetConid, database: targetDatabase }))
314
+ );
315
+ // const sourceConnection = await connections.get({conid:sourceConid})
316
+ const connection = await connections.get({ conid: targetConid });
317
+ const driver = requireEngineDriver(connection);
318
+ const targetDbPaired = matchPairedObjects(sourceDb, targetDb, dbDiffOptions);
319
+ const diffRows = computeDbDiffRows(sourceDb, targetDbPaired, dbDiffOptions, driver);
320
+
321
+ // console.log('sourceDb', sourceDb);
322
+ // console.log('targetDb', targetDb);
323
+ // console.log('sourceConid, sourceDatabase', sourceConid, sourceDatabase);
324
+
325
+ let res = '';
326
+ for (const row of diffRows) {
327
+ // console.log('PAIR', row.source && row.source.pureName, row.target && row.target.pureName);
328
+ const unifiedDiff = createTwoFilesPatch(
329
+ (row.target && row.target.pureName) || '',
330
+ (row.source && row.source.pureName) || '',
331
+ getCreateObjectScript(row.target, driver),
332
+ getCreateObjectScript(row.source, driver),
333
+ '',
334
+ ''
335
+ );
336
+ res += unifiedDiff;
337
+ }
338
+ return res;
339
+ },
340
+
341
+ generateDbDiffReport_meta: 'post',
342
+ async generateDbDiffReport({ filePath, sourceConid, sourceDatabase, targetConid, targetDatabase }) {
343
+ const unifiedDiff = await this.getUnifiedDiff({ sourceConid, sourceDatabase, targetConid, targetDatabase });
344
+
345
+ const diffJson = parse(unifiedDiff);
346
+ // $: diffHtml = html(diffJson, { outputFormat: 'side-by-side', drawFileList: false });
347
+ const diffHtml = html(diffJson, { outputFormat: 'side-by-side' });
348
+
349
+ await fs.writeFile(filePath, diff2htmlPage(diffHtml));
350
+
351
+ return true;
352
+ },
248
353
  };
@@ -1,6 +1,8 @@
1
+ const uuidv1 = require('uuid/v1');
1
2
  const fs = require('fs-extra');
2
3
  const path = require('path');
3
- const { filesdir } = require('../utility/directories');
4
+ const { filesdir, archivedir, resolveArchiveFolder, uploadsdir } = require('../utility/directories');
5
+ const getChartExport = require('../utility/getChartExport');
4
6
  const hasPermission = require('../utility/hasPermission');
5
7
  const socket = require('../utility/socket');
6
8
  const scheduler = require('./scheduler');
@@ -56,25 +58,46 @@ module.exports = {
56
58
  socket.emitChanged(`all-files-changed`);
57
59
  },
58
60
 
61
+ copy_meta: 'post',
62
+ async copy({ folder, file, newFile }) {
63
+ if (!hasPermission(`files/${folder}/write`)) return;
64
+ await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
65
+ socket.emitChanged(`files-changed-${folder}`);
66
+ socket.emitChanged(`all-files-changed`);
67
+ },
68
+
59
69
  load_meta: 'post',
60
70
  async load({ folder, file, format }) {
61
- if (!hasPermission(`files/${folder}/read`)) return null;
62
- const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' });
63
- return deserialize(format, text);
71
+ if (folder.startsWith('archive:')) {
72
+ const text = await fs.readFile(path.join(resolveArchiveFolder(folder.substring('archive:'.length)), file), {
73
+ encoding: 'utf-8',
74
+ });
75
+ return deserialize(format, text);
76
+ } else {
77
+ if (!hasPermission(`files/${folder}/read`)) return null;
78
+ const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' });
79
+ return deserialize(format, text);
80
+ }
64
81
  },
65
82
 
66
83
  save_meta: 'post',
67
84
  async save({ folder, file, data, format }) {
68
- if (!hasPermission(`files/${folder}/write`)) return;
69
- const dir = path.join(filesdir(), folder);
70
- if (!(await fs.exists(dir))) {
71
- await fs.mkdir(dir);
72
- }
73
- await fs.writeFile(path.join(dir, file), serialize(format, data));
74
- socket.emitChanged(`files-changed-${folder}`);
75
- socket.emitChanged(`all-files-changed`);
76
- if (folder == 'shell') {
77
- scheduler.reload();
85
+ if (folder.startsWith('archive:')) {
86
+ const dir = resolveArchiveFolder(folder.substring('archive:'.length));
87
+ await fs.writeFile(path.join(dir, file), serialize(format, data));
88
+ socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`);
89
+ } else {
90
+ if (!hasPermission(`files/${folder}/write`)) return;
91
+ const dir = path.join(filesdir(), folder);
92
+ if (!(await fs.exists(dir))) {
93
+ await fs.mkdir(dir);
94
+ }
95
+ await fs.writeFile(path.join(dir, file), serialize(format, data));
96
+ socket.emitChanged(`files-changed-${folder}`);
97
+ socket.emitChanged(`all-files-changed`);
98
+ if (folder == 'shell') {
99
+ scheduler.reload();
100
+ }
78
101
  }
79
102
  },
80
103
 
@@ -101,4 +124,30 @@ module.exports = {
101
124
  }
102
125
  return res;
103
126
  },
127
+
128
+ generateUploadsFile_meta: 'get',
129
+ async generateUploadsFile() {
130
+ const fileName = `${uuidv1()}.html`;
131
+ return {
132
+ fileName,
133
+ filePath: path.join(uploadsdir(), fileName),
134
+ };
135
+ },
136
+
137
+ exportChart_meta: 'post',
138
+ async exportChart({ filePath, title, config, image }) {
139
+ const fileName = path.parse(filePath).base;
140
+ const imageFile = fileName.replace('.html', '-preview.png');
141
+ const html = getChartExport(title, config, imageFile);
142
+ await fs.writeFile(filePath, html);
143
+ if (image) {
144
+ const index = image.indexOf('base64,');
145
+ if (index > 0) {
146
+ const data = image.substr(index + 'base64,'.length);
147
+ const buf = Buffer.from(data, 'base64');
148
+ await fs.writeFile(filePath.replace('.html', '-preview.png'), buf);
149
+ }
150
+ }
151
+ return true;
152
+ },
104
153
  };
@@ -15,17 +15,20 @@ function extractPlugins(script) {
15
15
  return matches.map(x => x[1]);
16
16
  }
17
17
 
18
- const requirePluginsTemplate = plugins =>
18
+ const requirePluginsTemplate = (plugins, isExport) =>
19
19
  plugins
20
20
  .map(
21
- packageName => `const ${_.camelCase(packageName)} = require(process.env.PLUGIN_${_.camelCase(packageName)});\n`
21
+ packageName =>
22
+ `const ${_.camelCase(packageName)} = require(${
23
+ isExport ? `'${packageName}'` : `process.env.PLUGIN_${_.camelCase(packageName)}`
24
+ });\n`
22
25
  )
23
26
  .join('') + `dbgateApi.registerPlugins(${plugins.map(x => _.camelCase(x)).join(',')});\n`;
24
27
 
25
- const scriptTemplate = script => `
26
- const dbgateApi = require(process.env.DBGATE_API);
28
+ const scriptTemplate = (script, isExport) => `
29
+ const dbgateApi = require(${isExport ? `'dbgate-api'` : 'process.env.DBGATE_API'});
27
30
  dbgateApi.initializeApiEnvironment();
28
- ${requirePluginsTemplate(extractPlugins(script))}
31
+ ${requirePluginsTemplate(extractPlugins(script), isExport)}
29
32
  require=null;
30
33
  async function run() {
31
34
  ${script}
@@ -139,7 +142,12 @@ module.exports = {
139
142
  start_meta: 'post',
140
143
  async start({ script }) {
141
144
  const runid = uuidv1();
142
- return this.startCore(runid, scriptTemplate(script));
145
+ return this.startCore(runid, scriptTemplate(script, false));
146
+ },
147
+
148
+ getNodeScript_meta: 'post',
149
+ async getNodeScript({ script }) {
150
+ return scriptTemplate(script, true);
143
151
  },
144
152
 
145
153
  cancel_meta: 'post',
@@ -25,4 +25,12 @@ module.exports = {
25
25
  });
26
26
  });
27
27
  },
28
+
29
+ get_meta: {
30
+ method: 'get',
31
+ raw: true,
32
+ },
33
+ get(req, res) {
34
+ res.sendFile(path.join(uploadsdir(), req.query.file));
35
+ },
28
36
  };
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '4.3.4',
4
- buildTime: '2021-10-31T06:53:04.996Z'
3
+ version: '4.4.1',
4
+ buildTime: '2021-11-22T18:42:53.218Z'
5
5
  };
@@ -1,9 +1,9 @@
1
1
  const path = require('path');
2
- const { archivedir } = require('../utility/directories');
2
+ const { archivedir, resolveArchiveFolder } = require('../utility/directories');
3
3
  const jsonLinesReader = require('./jsonLinesReader');
4
4
 
5
5
  function archiveReader({ folderName, fileName, ...other }) {
6
- const jsonlFile = path.join(archivedir(), folderName, `${fileName}.jsonl`);
6
+ const jsonlFile = path.join(resolveArchiveFolder(folderName), `${fileName}.jsonl`);
7
7
  const res = jsonLinesReader({ fileName: jsonlFile, ...other });
8
8
  return res;
9
9
  }
@@ -1,11 +1,11 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
- const { archivedir } = require('../utility/directories');
3
+ const { archivedir, resolveArchiveFolder } = require('../utility/directories');
4
4
  // const socket = require('../utility/socket');
5
5
  const jsonLinesWriter = require('./jsonLinesWriter');
6
6
 
7
7
  function archiveWriter({ folderName, fileName }) {
8
- const dir = path.join(archivedir(), folderName);
8
+ const dir = resolveArchiveFolder(folderName);
9
9
  if (!fs.existsSync(dir)) {
10
10
  console.log(`Creating directory ${dir}`);
11
11
  fs.mkdirSync(dir);
@@ -0,0 +1,17 @@
1
+ const generateDeploySql = require('./generateDeploySql');
2
+ const executeQuery = require('./executeQuery');
3
+
4
+ async function deployDb({ connection, systemConnection, driver, analysedStructure, modelFolder, loadedDbModel }) {
5
+ const { sql } = await generateDeploySql({
6
+ connection,
7
+ systemConnection,
8
+ driver,
9
+ analysedStructure,
10
+ modelFolder,
11
+ loadedDbModel,
12
+ });
13
+ // console.log('RUNNING DEPLOY SCRIPT:', sql);
14
+ await executeQuery({ connection, systemConnection, driver, sql });
15
+ }
16
+
17
+ module.exports = deployDb;
@@ -1,20 +1,14 @@
1
- const { splitQuery } = require('dbgate-query-splitter');
2
1
  const requireEngineDriver = require('../utility/requireEngineDriver');
3
2
  const connectUtility = require('../utility/connectUtility');
4
3
 
5
- async function executeQuery({ connection, sql }) {
4
+ async function executeQuery({ connection = undefined, systemConnection = undefined, driver = undefined, sql }) {
6
5
  console.log(`Execute query ${sql}`);
7
6
 
8
- const driver = requireEngineDriver(connection);
9
- const pool = await connectUtility(driver, connection);
7
+ if (!driver) driver = requireEngineDriver(connection);
8
+ const pool = systemConnection || (await connectUtility(driver, connection));
10
9
  console.log(`Connected.`);
11
10
 
12
- for (const sqlItem of splitQuery(sql, driver.getQuerySplitterOptions('script'))) {
13
- console.log('Executing query', sqlItem);
14
- await driver.query(pool, sqlItem, { discardResult: true });
15
- }
16
-
17
- console.log(`Query finished`);
11
+ await driver.script(pool, sql);
18
12
  }
19
13
 
20
14
  module.exports = executeQuery;
@@ -5,7 +5,7 @@ async function fakeObjectReader({ delay = 0 } = {}) {
5
5
  objectMode: true,
6
6
  });
7
7
  function doWrite() {
8
- pass.write({ columns: [{ columnName: 'id' }, { columnName: 'country' }] });
8
+ pass.write({ columns: [{ columnName: 'id' }, { columnName: 'country' }], __isStreamHeader: true });
9
9
  pass.write({ id: 1, country: 'Czechia' });
10
10
  pass.write({ id: 2, country: 'Austria' });
11
11
  pass.write({ country: 'Germany', id: 3 });
@@ -0,0 +1,49 @@
1
+ const {
2
+ getAlterDatabaseScript,
3
+ generateDbPairingId,
4
+ matchPairedObjects,
5
+ databaseInfoFromYamlModel,
6
+ extendDatabaseInfo,
7
+ modelCompareDbDiffOptions,
8
+ } = require('dbgate-tools');
9
+ const importDbModel = require('../utility/importDbModel');
10
+ const requireEngineDriver = require('../utility/requireEngineDriver');
11
+ const connectUtility = require('../utility/connectUtility');
12
+
13
+ async function generateDeploySql({
14
+ connection,
15
+ systemConnection = undefined,
16
+ driver = undefined,
17
+ analysedStructure = undefined,
18
+ modelFolder = undefined,
19
+ loadedDbModel = undefined,
20
+ }) {
21
+ if (!driver) driver = requireEngineDriver(connection);
22
+ if (!analysedStructure) {
23
+ const pool = systemConnection || (await connectUtility(driver, connection));
24
+ analysedStructure = await driver.analyseFull(pool);
25
+ }
26
+
27
+ const deployedModel = generateDbPairingId(
28
+ extendDatabaseInfo(loadedDbModel ? databaseInfoFromYamlModel(loadedDbModel) : await importDbModel(modelFolder))
29
+ );
30
+ const currentModel = generateDbPairingId(extendDatabaseInfo(analysedStructure));
31
+ const opts = {
32
+ ...modelCompareDbDiffOptions,
33
+
34
+ noDropTable: true,
35
+ noDropColumn: true,
36
+ noDropConstraint: true,
37
+ noDropSqlObject: true,
38
+ noRenameTable: true,
39
+ noRenameColumn: true,
40
+ };
41
+ const currentModelPaired = matchPairedObjects(deployedModel, currentModel, opts);
42
+ // console.log('deployedModel', deployedModel.tables[0]);
43
+ // console.log('currentModel', currentModel.tables[0]);
44
+ // console.log('currentModelPaired', currentModelPaired.tables[0]);
45
+ const res = getAlterDatabaseScript(currentModelPaired, deployedModel, opts, deployedModel, driver);
46
+ return res;
47
+ }
48
+
49
+ module.exports = generateDeploySql;
@@ -19,6 +19,7 @@ const requirePlugin = require('./requirePlugin');
19
19
  const download = require('./download');
20
20
  const executeQuery = require('./executeQuery');
21
21
  const loadFile = require('./loadFile');
22
+ const deployDb = require('./deployDb');
22
23
  const initializeApiEnvironment = require('./initializeApiEnvironment');
23
24
 
24
25
  const dbgateApi = {
@@ -42,6 +43,7 @@ const dbgateApi = {
42
43
  registerPlugins,
43
44
  executeQuery,
44
45
  loadFile,
46
+ deployDb,
45
47
  initializeApiEnvironment,
46
48
  };
47
49
 
@@ -1,13 +1,15 @@
1
1
  const { fullNameToString } = require('dbgate-tools');
2
2
  const requireEngineDriver = require('../utility/requireEngineDriver');
3
- const { decryptConnection } = require('../utility/crypting');
4
3
  const connectUtility = require('../utility/connectUtility');
5
4
 
6
- async function tableWriter({ connection, schemaName, pureName, ...options }) {
5
+ async function tableWriter({ connection, schemaName, pureName, driver, systemConnection, ...options }) {
7
6
  console.log(`Writing table ${fullNameToString({ schemaName, pureName })}`);
8
7
 
9
- const driver = requireEngineDriver(connection);
10
- const pool = await connectUtility(driver, connection);
8
+ if (!driver) {
9
+ driver = requireEngineDriver(connection);
10
+ }
11
+ const pool = systemConnection || (await connectUtility(driver, connection));
12
+
11
13
  console.log(`Connected.`);
12
14
  return await driver.writeTable(pool, { schemaName, pureName }, options);
13
15
  }
@@ -0,0 +1,8 @@
1
+ const diff2htmlCss =
2
+ '.d2h-d-none{display:none}.d2h-wrapper{text-align:left}.d2h-file-header{background-color:#f7f7f7;border-bottom:1px solid #d8d8d8;font-family:Source Sans Pro,Helvetica Neue,Helvetica,Arial,sans-serif;height:35px;padding:5px 10px}.d2h-file-header,.d2h-file-stats{display:-webkit-box;display:-ms-flexbox;display:flex}.d2h-file-stats{font-size:14px;margin-left:auto}.d2h-lines-added{border:1px solid #b4e2b4;border-radius:5px 0 0 5px;color:#399839;padding:2px;text-align:right;vertical-align:middle}.d2h-lines-deleted{border:1px solid #e9aeae;border-radius:0 5px 5px 0;color:#c33;margin-left:1px;padding:2px;text-align:left;vertical-align:middle}.d2h-file-name-wrapper{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;font-size:15px;width:100%}.d2h-file-name{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.d2h-file-wrapper{border:1px solid #ddd;border-radius:3px;margin-bottom:1em}.d2h-file-collapse{-webkit-box-pack:end;-ms-flex-pack:end;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #ddd;border-radius:3px;cursor:pointer;display:none;font-size:12px;justify-content:flex-end;padding:4px 8px}.d2h-file-collapse.d2h-selected{background-color:#c8e1ff}.d2h-file-collapse-input{margin:0 4px 0 0}.d2h-diff-table{border-collapse:collapse;font-family:Menlo,Consolas,monospace;font-size:13px;width:100%}.d2h-files-diff{width:100%}.d2h-file-diff{overflow-y:hidden}.d2h-file-side-diff{display:inline-block;margin-bottom:-8px;margin-right:-4px;overflow-x:scroll;overflow-y:hidden;width:50%}.d2h-code-line{padding:0 8em}.d2h-code-line,.d2h-code-side-line{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap;width:100%}.d2h-code-side-line{padding:0 4.5em}.d2h-code-line-ctn{word-wrap:normal;background:none;display:inline-block;padding:0;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;vertical-align:middle;white-space:pre;width:100%}.d2h-code-line del,.d2h-code-side-line del{background-color:#ffb6ba}.d2h-code-line del,.d2h-code-line ins,.d2h-code-side-line del,.d2h-code-side-line ins{border-radius:.2em;display:inline-block;margin-top:-1px;text-decoration:none;vertical-align:middle}.d2h-code-line ins,.d2h-code-side-line ins{background-color:#97f295;text-align:left}.d2h-code-line-prefix{word-wrap:normal;background:none;display:inline;padding:0;white-space:pre}.line-num1{float:left}.line-num1,.line-num2{-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;padding:0 .5em;text-overflow:ellipsis;width:3.5em}.line-num2{float:right}.d2h-code-linenumber{background-color:#fff;border:solid #eee;border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);cursor:pointer;display:inline-block;position:absolute;text-align:right;width:7.5em}.d2h-code-linenumber:after{content:"\200b"}.d2h-code-side-linenumber{background-color:#fff;border:solid #eee;border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);cursor:pointer;display:inline-block;overflow:hidden;padding:0 .5em;position:absolute;text-align:right;text-overflow:ellipsis;width:4em}.d2h-code-side-linenumber:after{content:"\200b"}.d2h-code-side-emptyplaceholder,.d2h-emptyplaceholder{background-color:#f1f1f1;border-color:#e1e1e1}.d2h-code-line-prefix,.d2h-code-linenumber,.d2h-code-side-linenumber,.d2h-emptyplaceholder{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.d2h-code-linenumber,.d2h-code-side-linenumber{direction:rtl}.d2h-del{background-color:#fee8e9;border-color:#e9aeae}.d2h-ins{background-color:#dfd;border-color:#b4e2b4}.d2h-info{background-color:#f8fafd;border-color:#d5e4f2;color:rgba(0,0,0,.3)}.d2h-file-diff .d2h-del.d2h-change{background-color:#fdf2d0}.d2h-file-diff .d2h-ins.d2h-change{background-color:#ded}.d2h-file-list-wrapper{margin-bottom:10px}.d2h-file-list-wrapper a{color:#3572b0;text-decoration:none}.d2h-file-list-wrapper a:visited{color:#3572b0}.d2h-file-list-header{text-align:left}.d2h-file-list-title{font-weight:700}.d2h-file-list-line{display:-webkit-box;display:-ms-flexbox;display:flex;text-align:left}.d2h-file-list{display:block;list-style:none;margin:0;padding:0}.d2h-file-list>li{border-bottom:1px solid #ddd;margin:0;padding:5px 10px}.d2h-file-list>li:last-child{border-bottom:none}.d2h-file-switch{cursor:pointer;display:none;font-size:10px}.d2h-icon{fill:currentColor;margin-right:10px;vertical-align:middle}.d2h-deleted{color:#c33}.d2h-added{color:#399839}.d2h-changed{color:#d0b44c}.d2h-moved{color:#3572b0}.d2h-tag{background-color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;font-size:10px;margin-left:5px;padding:0 2px}.d2h-deleted-tag{border:1px solid #c33}.d2h-added-tag{border:1px solid #399839}.d2h-changed-tag{border:1px solid #d0b44c}.d2h-moved-tag{border:1px solid #3572b0}';
3
+
4
+ function diff2htmlPage(content) {
5
+ return `<html><head><style>${diff2htmlCss}</style><body>${content}</body></html>`;
6
+ }
7
+
8
+ module.exports = diff2htmlPage;
@@ -71,6 +71,22 @@ function getPluginBackendPath(packageName) {
71
71
  return path.join(pluginsdir(), packageName, 'dist', 'backend.js');
72
72
  }
73
73
 
74
+ let archiveLinksCache = {};
75
+
76
+ function resolveArchiveFolder(folder) {
77
+ if (folder.endsWith('.link')) {
78
+ if (!archiveLinksCache[folder]) {
79
+ archiveLinksCache[folder] = fs.readFileSync(path.join(archivedir(), folder), 'utf-8');
80
+ }
81
+ return archiveLinksCache[folder];
82
+ }
83
+ return path.join(archivedir(), folder);
84
+ }
85
+
86
+ function clearArchiveLinksCache() {
87
+ archiveLinksCache = {};
88
+ }
89
+
74
90
  module.exports = {
75
91
  datadir,
76
92
  jsldir,
@@ -83,4 +99,6 @@ module.exports = {
83
99
  packagedPluginsDir,
84
100
  packagedPluginList,
85
101
  getPluginBackendPath,
102
+ resolveArchiveFolder,
103
+ clearArchiveLinksCache,
86
104
  };
@@ -0,0 +1,31 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const yaml = require('js-yaml');
4
+ const { tableInfoToYaml } = require('dbgate-tools');
5
+
6
+ async function exportDbModel(dbModel, outputDir) {
7
+ const { tables, views, procedures, functions, triggers, matviews } = dbModel;
8
+
9
+ if (!fs.existsSync(outputDir)) {
10
+ await fs.mkdir(outputDir);
11
+ }
12
+
13
+ for (const table of tables || []) {
14
+ const content = yaml.dump(tableInfoToYaml(table));
15
+ await fs.writeFile(path.join(outputDir, `${table.pureName}.table.yaml`), content);
16
+ }
17
+
18
+ async function writeList(list, ext) {
19
+ for (const obj of list || []) {
20
+ await fs.writeFile(path.join(outputDir, `${obj.pureName}.${ext}.sql`), obj.createSql);
21
+ }
22
+ }
23
+
24
+ await writeList(views, 'view');
25
+ await writeList(procedures, 'proc');
26
+ await writeList(functions, 'func');
27
+ await writeList(triggers, 'trigger');
28
+ await writeList(matviews, 'matview');
29
+ }
30
+
31
+ module.exports = exportDbModel;
@@ -0,0 +1,52 @@
1
+ const getChartExport = (title, config, imageFile) => {
2
+ return `<html>
3
+ <meta charset='utf-8'>
4
+
5
+ <head>
6
+ ${title ? `<title>${title}</title>` : ''}
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.0/chart.min.js" integrity="sha512-GMGzUEevhWh8Tc/njS0bDpwgxdCJLQBWG3Z2Ct+JGOpVnEmjvNx6ts4v6A2XJf1HOrtOsfhv3hBKpK9kE5z8AQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" integrity="sha512-qTXRIMyZIFb8iQcfjXWCO8+M5Tbc38Qi5WzdPOYZHIlZpzBHG3L3by84BBBOiRGiEb7KKtAOAs5qYdUiZiQNNQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-adapter-moment/1.0.0/chartjs-adapter-moment.min.js" integrity="sha512-oh5t+CdSBsaVVAvxcZKy3XJdP7ZbYUBSRCXDTVn0ODewMDDNnELsrG9eDm8rVZAQg7RsDD/8K3MjPAFB13o6eA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
10
+ <style>
11
+ a { text-decoration: none }
12
+
13
+ .footer {
14
+ float: right;
15
+ font-family: Arial;
16
+ color: #888;
17
+ margin-top: 10px;
18
+ margin-right: 10px;
19
+ font-size: 10pt;
20
+ }
21
+ </style>
22
+
23
+ <script>
24
+ const config = ${JSON.stringify(config)};
25
+
26
+ function showChart() {
27
+ document.getElementById('myImage').style.display = "none";
28
+
29
+ const myChart = new Chart(
30
+ document.getElementById('myChart'),
31
+ config
32
+ );
33
+ }
34
+ </script>
35
+ </head>
36
+
37
+ <body onload="showChart()">
38
+ <img src="${imageFile}" id="myImage" />
39
+
40
+ <div>
41
+ <canvas id="myChart"></canvas>
42
+ </div>
43
+
44
+ <div class="footer">
45
+ Exported from <a href='https://dbgate.org/' target='_blank'>DbGate</a>, powered by <a href='https://www.chartjs.org/' target='_blank'>Chart.js</a>
46
+ </div>
47
+ </body>
48
+
49
+ </html>`;
50
+ };
51
+
52
+ module.exports = getChartExport;
@@ -1,10 +1,10 @@
1
1
  const path = require('path');
2
- const { jsldir, archivedir } = require('./directories');
2
+ const { jsldir, archivedir, resolveArchiveFolder } = require('./directories');
3
3
 
4
4
  function getJslFileName(jslid) {
5
5
  const archiveMatch = jslid.match(/^archive:\/\/([^/]+)\/(.*)$/);
6
6
  if (archiveMatch) {
7
- return path.join(archivedir(), archiveMatch[1], `${archiveMatch[2]}.jsonl`);
7
+ return path.join(resolveArchiveFolder(archiveMatch[1]), `${archiveMatch[2]}.jsonl`);
8
8
  }
9
9
  return path.join(jsldir(), `${jslid}.jsonl`);
10
10
  }
@@ -0,0 +1,29 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const yaml = require('js-yaml');
4
+ const { databaseInfoFromYamlModel, DatabaseAnalyser } = require('dbgate-tools');
5
+ const { startsWith } = require('lodash');
6
+ const { archivedir, resolveArchiveFolder } = require('./directories');
7
+ const loadFilesRecursive = require('./loadFilesRecursive');
8
+
9
+ async function importDbModel(inputDir) {
10
+ const files = [];
11
+
12
+ const dir = inputDir.startsWith('archive:') ? resolveArchiveFolder(inputDir.substring('archive:'.length)) : inputDir;
13
+
14
+ for (const name of await loadFilesRecursive(dir)) {
15
+ if (name.endsWith('.table.yaml') || name.endsWith('.sql')) {
16
+ const text = await fs.readFile(path.join(dir, name), { encoding: 'utf-8' });
17
+
18
+ files.push({
19
+ name: path.parse(name).base,
20
+ text,
21
+ json: name.endsWith('.yaml') ? yaml.load(text) : null,
22
+ });
23
+ }
24
+ }
25
+
26
+ return databaseInfoFromYamlModel(files);
27
+ }
28
+
29
+ module.exports = importDbModel;
@@ -0,0 +1,20 @@
1
+ const fs = require('fs-extra');
2
+ const stream = require('stream');
3
+ const readline = require('readline');
4
+ const path = require('path');
5
+
6
+ async function loadFilesRecursive(dir, root = null) {
7
+ if (!root) root = dir;
8
+ if (!root.endsWith(path.sep)) root += path.sep;
9
+ const dirents = await fs.readdir(dir, { withFileTypes: true });
10
+ const files = await Promise.all(
11
+ dirents.map(dirent => {
12
+ const res = path.join(dir, dirent.name);
13
+ return dirent.isDirectory() ? loadFilesRecursive(res, root) : res;
14
+ })
15
+ );
16
+ const flatten = Array.prototype.concat(...files);
17
+ return flatten.map(file => (file.startsWith(root) ? file.substr(root.length) : file));
18
+ }
19
+
20
+ module.exports = loadFilesRecursive;