mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-24 23:26:00 +00:00
Merge branch 'plugins'
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dbgate",
|
||||
"version": "3.7.33",
|
||||
"version": "3.8.0",
|
||||
"private": true,
|
||||
"author": "Jan Prochazka <jenasoft.database@gmail.com>",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
jquery: false,
|
||||
jest: true,
|
||||
jasmine: true,
|
||||
},
|
||||
extends: 'eslint:recommended',
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly',
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': 'warn',
|
||||
},
|
||||
};
|
||||
@@ -12,11 +12,6 @@
|
||||
"license": "GPL",
|
||||
"keywords": [
|
||||
"sql",
|
||||
"mssql",
|
||||
"mssql",
|
||||
"postgresql",
|
||||
"csv",
|
||||
"excel",
|
||||
"json",
|
||||
"import",
|
||||
"export",
|
||||
@@ -30,8 +25,6 @@
|
||||
"byline": "^5.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^6.0.3",
|
||||
"csv": "^5.3.2",
|
||||
"dbgate-engines": "^1.0.0",
|
||||
"dbgate-sqltree": "^1.0.0",
|
||||
"dbgate-tools": "^1.0.0",
|
||||
"eslint": "^6.8.0",
|
||||
@@ -43,12 +36,9 @@
|
||||
"http": "^0.0.0",
|
||||
"line-reader": "^0.4.0",
|
||||
"lodash": "^4.17.15",
|
||||
"mssql": "^6.0.1",
|
||||
"mysql": "^2.17.1",
|
||||
"ncp": "^2.0.0",
|
||||
"nedb-promises": "^4.0.1",
|
||||
"pg": "^7.17.0",
|
||||
"pg-query-stream": "^3.1.1",
|
||||
"xlsx": "^0.16.8"
|
||||
"tar": "^6.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "nodemon src/index.js",
|
||||
|
||||
@@ -2,7 +2,7 @@ const uuidv1 = require('uuid/v1');
|
||||
const connections = require('./connections');
|
||||
const socket = require('../utility/socket');
|
||||
const { fork } = require('child_process');
|
||||
const DatabaseAnalyser = require('dbgate-engines/default/DatabaseAnalyser');
|
||||
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||
|
||||
module.exports = {
|
||||
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
const xlsx = require('xlsx');
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
openedReaders: {},
|
||||
|
||||
analyseExcel_meta: 'get',
|
||||
async analyseExcel({ filePath }) {
|
||||
const workbook = xlsx.readFile(filePath, { bookSheets: true });
|
||||
return workbook.SheetNames;
|
||||
},
|
||||
};
|
||||
150
packages/api/src/controllers/plugins.js
Normal file
150
packages/api/src/controllers/plugins.js
Normal file
@@ -0,0 +1,150 @@
|
||||
const fs = require('fs-extra');
|
||||
const axios = require('axios');
|
||||
const path = require('path');
|
||||
const { pluginsdir, datadir } = require('../utility/directories');
|
||||
const socket = require('../utility/socket');
|
||||
const requirePlugin = require('../shell/requirePlugin');
|
||||
const downloadPackage = require('../utility/downloadPackage');
|
||||
|
||||
// async function loadPackageInfo(dir) {
|
||||
// const readmeFile = path.join(dir, 'README.md');
|
||||
// const packageFile = path.join(dir, 'package.json');
|
||||
|
||||
// if (!(await fs.exists(packageFile))) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// let readme = null;
|
||||
// let manifest = null;
|
||||
// if (await fs.exists(readmeFile)) readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
|
||||
// if (await fs.exists(packageFile)) manifest = JSON.parse(await fs.readFile(packageFile, { encoding: 'utf-8' }));
|
||||
// return {
|
||||
// readme,
|
||||
// manifest,
|
||||
// };
|
||||
// }
|
||||
|
||||
const preinstallPlugins = [
|
||||
'dbgate-plugin-mssql',
|
||||
'dbgate-plugin-mysql',
|
||||
'dbgate-plugin-postgres',
|
||||
'dbgate-plugin-csv',
|
||||
'dbgate-plugin-excel',
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
script_meta: 'get',
|
||||
async script({ packageName }) {
|
||||
const file = path.join(pluginsdir(), packageName, 'dist', 'frontend.js');
|
||||
const data = await fs.readFile(file, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
search_meta: 'get',
|
||||
async search({ filter }) {
|
||||
// DOCS: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#get-v1search
|
||||
const resp = await axios.default.get(
|
||||
`http://registry.npmjs.com/-/v1/search?text=${encodeURIComponent(filter)}+keywords:dbgateplugin&size=25&from=0`
|
||||
);
|
||||
const { objects } = resp.data || {};
|
||||
return (objects || []).map((x) => x.package);
|
||||
},
|
||||
|
||||
info_meta: 'get',
|
||||
async info({ packageName }) {
|
||||
try {
|
||||
const infoResp = await axios.default.get(`https://registry.npmjs.org/${packageName}`);
|
||||
const { latest } = infoResp.data['dist-tags'];
|
||||
const manifest = infoResp.data.versions[latest];
|
||||
const { readme } = infoResp.data;
|
||||
|
||||
return {
|
||||
readme,
|
||||
manifest,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
state: 'error',
|
||||
error: err.message,
|
||||
};
|
||||
}
|
||||
|
||||
// const dir = path.join(pluginstmpdir(), packageName);
|
||||
// if (!(await fs.exists(dir))) {
|
||||
// await downloadPackage(packageName, dir);
|
||||
// }
|
||||
// return await loadPackageInfo(dir);
|
||||
// return await {
|
||||
// ...loadPackageInfo(dir),
|
||||
// installed: loadPackageInfo(path.join(pluginsdir(), packageName)),
|
||||
// };
|
||||
},
|
||||
|
||||
installed_meta: 'get',
|
||||
async installed() {
|
||||
const files = await fs.readdir(pluginsdir());
|
||||
const res = [];
|
||||
for (const packageName of files) {
|
||||
const manifest = await fs
|
||||
.readFile(path.join(pluginsdir(), packageName, 'package.json'))
|
||||
.then((x) => JSON.parse(x));
|
||||
const readmeFile = path.join(pluginsdir(), packageName, 'README.md');
|
||||
if (await fs.exists(readmeFile)) {
|
||||
manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
|
||||
}
|
||||
res.push(manifest);
|
||||
}
|
||||
return res;
|
||||
// const res = await Promise.all(
|
||||
// files.map((packageName) =>
|
||||
// fs.readFile(path.join(pluginsdir(), packageName, 'package.json')).then((x) => JSON.parse(x))
|
||||
// )
|
||||
// );
|
||||
},
|
||||
|
||||
install_meta: 'post',
|
||||
async install({ packageName }) {
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
if (!(await fs.exists(dir))) {
|
||||
await downloadPackage(packageName, dir);
|
||||
}
|
||||
socket.emitChanged(`installed-plugins-changed`);
|
||||
},
|
||||
|
||||
uninstall_meta: 'post',
|
||||
async uninstall({ packageName }) {
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
await fs.rmdir(dir, { recursive: true });
|
||||
socket.emitChanged(`installed-plugins-changed`);
|
||||
this.removedPlugins.push(packageName);
|
||||
await fs.writeFile(path.join(datadir(), 'removed-plugins'), this.removedPlugins.join('\n'));
|
||||
},
|
||||
|
||||
command_meta: 'post',
|
||||
async command({ packageName, command, args }) {
|
||||
const content = requirePlugin(packageName);
|
||||
return content.commands[command](args);
|
||||
},
|
||||
|
||||
async _init() {
|
||||
const installed = await this.installed();
|
||||
try {
|
||||
this.removedPlugins = (await fs.readFile(path.join(datadir(), 'removed-plugins'), { encoding: 'utf-8' })).split(
|
||||
'\n'
|
||||
);
|
||||
} catch (err) {
|
||||
this.removedPlugins = [];
|
||||
}
|
||||
for (const packageName of preinstallPlugins) {
|
||||
if (this.removedPlugins.includes(packageName)) continue;
|
||||
try {
|
||||
console.log('Preinstalling plugin', packageName);
|
||||
await this.install({ packageName });
|
||||
} catch (err) {
|
||||
console.error('Error preinstalling plugin', packageName, err);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -5,10 +5,25 @@ const uuidv1 = require('uuid/v1');
|
||||
const byline = require('byline');
|
||||
const socket = require('../utility/socket');
|
||||
const { fork } = require('child_process');
|
||||
const { rundir, uploadsdir } = require('../utility/directories');
|
||||
const { rundir, uploadsdir, pluginsdir } = require('../utility/directories');
|
||||
const { extractShellApiPlugins, extractShellApiFunctionName } = require('dbgate-tools');
|
||||
|
||||
function extractPlugins(script) {
|
||||
const requireRegex = /\s*\/\/\s*@require\s+([^\s]+)\s*\n/g;
|
||||
const matches = [...script.matchAll(requireRegex)];
|
||||
return matches.map((x) => x[1]);
|
||||
}
|
||||
|
||||
const requirePluginsTemplate = (plugins) =>
|
||||
plugins
|
||||
.map(
|
||||
(packageName) => `const ${_.camelCase(packageName)} = require(process.env.PLUGIN_${_.camelCase(packageName)});\n`
|
||||
)
|
||||
.join('') + `dbgateApi.registerPlugins(${plugins.map((x) => _.camelCase(x)).join(',')});\n`;
|
||||
|
||||
const scriptTemplate = (script) => `
|
||||
const dbgateApi = require(process.env.DBGATE_API || "dbgate-api");
|
||||
const dbgateApi = require(process.env.DBGATE_API);
|
||||
${requirePluginsTemplate(extractPlugins(script))}
|
||||
require=null;
|
||||
async function run() {
|
||||
${script}
|
||||
@@ -19,10 +34,11 @@ dbgateApi.runScript(run);
|
||||
`;
|
||||
|
||||
const loaderScriptTemplate = (functionName, props, runid) => `
|
||||
const dbgateApi = require(process.env.DBGATE_API || "dbgate-api");
|
||||
const dbgateApi = require(process.env.DBGATE_API);
|
||||
${requirePluginsTemplate(extractShellApiPlugins(functionName, props))}
|
||||
require=null;
|
||||
async function run() {
|
||||
const reader=await dbgateApi.${functionName}(${JSON.stringify(props)});
|
||||
const reader=await ${extractShellApiFunctionName(functionName)}(${JSON.stringify(props)});
|
||||
const writer=await dbgateApi.collectorWriter({runid: '${runid}'});
|
||||
await dbgateApi.copyStream(reader, writer);
|
||||
}
|
||||
@@ -73,12 +89,14 @@ module.exports = {
|
||||
const scriptFile = path.join(uploadsdir(), runid + '.js');
|
||||
fs.writeFileSync(`${scriptFile}`, scriptText);
|
||||
fs.mkdirSync(directory);
|
||||
const pluginNames = fs.readdirSync(pluginsdir());
|
||||
console.log(`RUNNING SCRIPT ${scriptFile}`);
|
||||
const subprocess = fork(scriptFile, ['--checkParent'], {
|
||||
cwd: directory,
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
||||
env: {
|
||||
DBGATE_API: process.argv[1],
|
||||
..._.fromPairs(pluginNames.map((name) => [`PLUGIN_${_.camelCase(name)}`, path.join(pluginsdir(), name)])),
|
||||
},
|
||||
});
|
||||
const pipeDispatcher = (severity) => (data) =>
|
||||
@@ -153,4 +171,3 @@ module.exports = {
|
||||
return promise;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,21 +2,6 @@ const path = require('path');
|
||||
const { uploadsdir } = require('../utility/directories');
|
||||
const uuidv1 = require('uuid/v1');
|
||||
|
||||
const extensions = [
|
||||
{
|
||||
ext: '.xlsx',
|
||||
type: 'excel',
|
||||
},
|
||||
{
|
||||
ext: '.jsonl',
|
||||
type: 'jsonl',
|
||||
},
|
||||
{
|
||||
ext: '.csv',
|
||||
type: 'csv',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
upload_meta: {
|
||||
method: 'post',
|
||||
@@ -31,19 +16,10 @@ module.exports = {
|
||||
const uploadName = uuidv1();
|
||||
const filePath = path.join(uploadsdir(), uploadName);
|
||||
console.log(`Uploading file ${data.name}, size=${data.size}`);
|
||||
let storageType = null;
|
||||
let shortName = data.name;
|
||||
for (const { ext, type } of extensions) {
|
||||
if (data.name.endsWith(ext)) {
|
||||
storageType = type;
|
||||
shortName = data.name.slice(0, -ext.length);
|
||||
}
|
||||
}
|
||||
|
||||
data.mv(filePath, () => {
|
||||
res.json({
|
||||
originalName: data.name,
|
||||
shortName,
|
||||
storageType,
|
||||
uploadName,
|
||||
filePath,
|
||||
});
|
||||
|
||||
@@ -20,9 +20,9 @@ const sessions = require('./controllers/sessions');
|
||||
const runners = require('./controllers/runners');
|
||||
const jsldata = require('./controllers/jsldata');
|
||||
const config = require('./controllers/config');
|
||||
const files = require('./controllers/files');
|
||||
const archive = require('./controllers/archive');
|
||||
const uploads = require('./controllers/uploads');
|
||||
const plugins = require('./controllers/plugins');
|
||||
|
||||
const { rundir } = require('./utility/directories');
|
||||
|
||||
@@ -64,9 +64,9 @@ function start(argument = null) {
|
||||
useController(app, '/runners', runners);
|
||||
useController(app, '/jsldata', jsldata);
|
||||
useController(app, '/config', config);
|
||||
useController(app, '/files', files);
|
||||
useController(app, '/archive', archive);
|
||||
useController(app, '/uploads', uploads);
|
||||
useController(app, '/plugins', plugins);
|
||||
|
||||
if (process.env.PAGES_DIRECTORY) {
|
||||
app.use('/pages', express.static(process.env.PAGES_DIRECTORY));
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
const engines = require('dbgate-engines');
|
||||
const driverConnect = require('../utility/driverConnect');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
process.on('message', async (connection) => {
|
||||
try {
|
||||
const driver = engines(connection);
|
||||
const conn = await driverConnect(driver, connection);
|
||||
const driver = requireEngineDriver(connection);
|
||||
const conn = await driver.connect(connection);
|
||||
const res = await driver.getVersion(conn);
|
||||
process.send({ msgtype: 'connected', ...res });
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const engines = require('dbgate-engines');
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const driverConnect = require('../utility/driverConnect');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
|
||||
let systemConnection;
|
||||
let storedConnection;
|
||||
@@ -26,14 +25,14 @@ async function checkedAsyncCall(promise) {
|
||||
}
|
||||
|
||||
async function handleFullRefresh() {
|
||||
const driver = engines(storedConnection);
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection));
|
||||
process.send({ msgtype: 'structure', structure: analysedStructure });
|
||||
setStatusName('ok');
|
||||
}
|
||||
|
||||
async function handleIncrementalRefresh() {
|
||||
const driver = engines(storedConnection);
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure));
|
||||
if (newStructure != null) {
|
||||
analysedStructure = newStructure;
|
||||
@@ -58,8 +57,8 @@ async function handleConnect({ connection, structure }) {
|
||||
lastPing = new Date().getTime();
|
||||
|
||||
if (!structure) setStatusName('pending');
|
||||
const driver = engines(storedConnection);
|
||||
systemConnection = await checkedAsyncCall(driverConnect(driver, storedConnection));
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await checkedAsyncCall(driver.connect(storedConnection));
|
||||
if (structure) {
|
||||
analysedStructure = structure;
|
||||
handleIncrementalRefresh();
|
||||
@@ -82,7 +81,7 @@ function waitConnected() {
|
||||
|
||||
async function handleQueryData({ msgid, sql }) {
|
||||
await waitConnected();
|
||||
const driver = engines(storedConnection);
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
const res = await driver.query(systemConnection, sql);
|
||||
process.send({ msgtype: 'response', msgid, ...res });
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const engines = require('dbgate-engines');
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const driverConnect = require('../utility/driverConnect');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
|
||||
let systemConnection;
|
||||
let storedConnection;
|
||||
@@ -10,7 +9,7 @@ let lastStatus = null;
|
||||
let lastPing = null;
|
||||
|
||||
async function handleRefresh() {
|
||||
const driver = engines(storedConnection);
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
const databases = await driver.listDatabases(systemConnection);
|
||||
setStatusName('ok');
|
||||
@@ -46,9 +45,9 @@ async function handleConnect(connection) {
|
||||
setStatusName('pending');
|
||||
lastPing = new Date().getTime();
|
||||
|
||||
const driver = engines(storedConnection);
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
systemConnection = await driverConnect(driver, storedConnection);
|
||||
systemConnection = await driver.connect(storedConnection);
|
||||
handleRefresh();
|
||||
setInterval(handleRefresh, 30 * 1000);
|
||||
} catch (err) {
|
||||
@@ -66,8 +65,8 @@ function handlePing() {
|
||||
}
|
||||
|
||||
async function handleCreateDatabase({ name }) {
|
||||
const driver = engines(storedConnection);
|
||||
systemConnection = await driverConnect(driver, storedConnection);
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await driver.connect(storedConnection);
|
||||
console.log(`RUNNING SCRIPT: CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
||||
await driver.query(systemConnection, `CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
||||
await handleRefresh();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const engines = require('dbgate-engines');
|
||||
const uuidv1 = require('uuid/v1');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
@@ -6,8 +5,8 @@ const _ = require('lodash');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const goSplit = require('../utility/goSplit');
|
||||
|
||||
const driverConnect = require('../utility/driverConnect');
|
||||
const { jsldir } = require('../utility/directories');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
|
||||
let systemConnection;
|
||||
let storedConnection;
|
||||
@@ -119,8 +118,8 @@ class StreamHandler {
|
||||
async function handleConnect(connection) {
|
||||
storedConnection = connection;
|
||||
|
||||
const driver = engines(storedConnection);
|
||||
systemConnection = await driverConnect(driver, storedConnection);
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await driver.connect(storedConnection);
|
||||
for (const [resolve] of afterConnectCallbacks) {
|
||||
resolve();
|
||||
}
|
||||
@@ -142,7 +141,7 @@ function waitConnected() {
|
||||
|
||||
async function handleExecuteQuery({ sql }) {
|
||||
await waitConnected();
|
||||
const driver = engines(storedConnection);
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
|
||||
let resultIndex = 0;
|
||||
for (const sqlItem of goSplit(sql)) {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
const csv = require('csv');
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
|
||||
class CsvPrepareStream extends stream.Transform {
|
||||
constructor({ header }) {
|
||||
super({ objectMode: true });
|
||||
this.structure = null;
|
||||
this.header = header;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
if (this.structure) {
|
||||
this.push(
|
||||
_.zipObject(
|
||||
this.structure.columns.map((x) => x.columnName),
|
||||
chunk
|
||||
)
|
||||
);
|
||||
done();
|
||||
} else {
|
||||
if (this.header) {
|
||||
this.structure = { columns: chunk.map((columnName) => ({ columnName })) };
|
||||
this.push(this.structure);
|
||||
} else {
|
||||
this.structure = { columns: chunk.map((value, index) => ({ columnName: `col${index + 1}` })) };
|
||||
this.push(this.structure);
|
||||
this.push(
|
||||
_.zipObject(
|
||||
this.structure.columns.map((x) => x.columnName),
|
||||
chunk
|
||||
)
|
||||
);
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function csvReader({ fileName, encoding = 'utf-8', header = true, delimiter, limitRows = undefined }) {
|
||||
console.log(`Reading file ${fileName}`);
|
||||
const csvStream = csv.parse({
|
||||
// @ts-ignore
|
||||
delimiter,
|
||||
skip_lines_with_error: true,
|
||||
to_line: limitRows ? limitRows + 1 : -1,
|
||||
});
|
||||
const fileStream = fs.createReadStream(fileName, encoding);
|
||||
const csvPrepare = new CsvPrepareStream({ header });
|
||||
fileStream.pipe(csvStream);
|
||||
csvStream.pipe(csvPrepare);
|
||||
return csvPrepare;
|
||||
}
|
||||
|
||||
module.exports = csvReader;
|
||||
@@ -1,36 +0,0 @@
|
||||
const csv = require('csv');
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
|
||||
class CsvPrepareStream extends stream.Transform {
|
||||
constructor({ header }) {
|
||||
super({ objectMode: true });
|
||||
this.structure = null;
|
||||
this.header = header;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
if (this.structure) {
|
||||
this.push(this.structure.columns.map((col) => chunk[col.columnName]));
|
||||
done();
|
||||
} else {
|
||||
this.structure = chunk;
|
||||
if (this.header) {
|
||||
this.push(chunk.columns.map((x) => x.columnName));
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function csvWriter({ fileName, encoding = 'utf-8', header = true, delimiter, quoted }) {
|
||||
console.log(`Writing file ${fileName}`);
|
||||
const csvPrepare = new CsvPrepareStream({ header });
|
||||
const csvStream = csv.stringify({ delimiter, quoted });
|
||||
const fileStream = fs.createWriteStream(fileName, encoding);
|
||||
csvPrepare.pipe(csvStream);
|
||||
csvStream.pipe(fileStream);
|
||||
csvPrepare['finisher'] = fileStream;
|
||||
return csvPrepare;
|
||||
}
|
||||
|
||||
module.exports = csvWriter;
|
||||
@@ -1,41 +0,0 @@
|
||||
const xlsx = require('xlsx');
|
||||
const stream = require('stream');
|
||||
const _ = require('lodash');
|
||||
|
||||
const loadedWorkbooks = {};
|
||||
|
||||
async function loadWorkbook(fileName) {
|
||||
let workbook = loadedWorkbooks[fileName];
|
||||
if (workbook) return workbook;
|
||||
console.log(`Loading excel ${fileName}`);
|
||||
workbook = xlsx.readFile(fileName);
|
||||
loadedWorkbooks[fileName] = workbook;
|
||||
return workbook;
|
||||
}
|
||||
|
||||
async function excelSheetReader({ fileName, sheetName, limitRows = undefined }) {
|
||||
const workbook = await loadWorkbook(fileName);
|
||||
const sheet = workbook.Sheets[sheetName];
|
||||
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
});
|
||||
const rows = xlsx.utils.sheet_to_json(sheet, { header: 1 });
|
||||
const header = rows[0];
|
||||
const structure = {
|
||||
columns: _.range(header.length).map((index) => ({ columnName: header[index] })),
|
||||
};
|
||||
pass.write(structure);
|
||||
for (let rowIndex = 1; rowIndex < rows.length; rowIndex++) {
|
||||
if (limitRows && rowIndex > limitRows) break;
|
||||
const row = rows[rowIndex];
|
||||
const rowData = _.fromPairs(structure.columns.map((col, index) => [col.columnName, row[index]]));
|
||||
if (_.isEmpty(_.omitBy(rowData, (v) => v == null || v.toString().trim().length == 0))) continue;
|
||||
pass.write(rowData);
|
||||
}
|
||||
pass.end();
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
module.exports = excelSheetReader;
|
||||
@@ -1,56 +0,0 @@
|
||||
const xlsx = require('xlsx');
|
||||
const stream = require('stream');
|
||||
const finalizer = require('./finalizer');
|
||||
|
||||
const writingWorkbooks = {};
|
||||
|
||||
async function saveExcelFiles() {
|
||||
for (const file in writingWorkbooks) {
|
||||
xlsx.writeFile(writingWorkbooks[file], file);
|
||||
}
|
||||
}
|
||||
|
||||
finalizer.register(saveExcelFiles);
|
||||
|
||||
function createWorkbook(fileName) {
|
||||
let workbook = writingWorkbooks[fileName];
|
||||
if (workbook) return workbook;
|
||||
workbook = xlsx.utils.book_new();
|
||||
writingWorkbooks[fileName] = workbook;
|
||||
return workbook;
|
||||
}
|
||||
|
||||
class ExcelSheetWriterStream extends stream.Writable {
|
||||
constructor({ fileName, sheetName }) {
|
||||
super({ objectMode: true });
|
||||
this.rows = [];
|
||||
this.structure = null;
|
||||
this.fileName = fileName;
|
||||
this.sheetName = sheetName;
|
||||
}
|
||||
_write(chunk, enc, next) {
|
||||
if (this.structure) {
|
||||
this.rows.push(this.structure.columns.map((col) => chunk[col.columnName]));
|
||||
} else {
|
||||
this.structure = chunk;
|
||||
this.rows.push(chunk.columns.map((x) => x.columnName));
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
_final(callback) {
|
||||
const workbook = createWorkbook(this.fileName);
|
||||
xlsx.utils.book_append_sheet(workbook, xlsx.utils.aoa_to_sheet(this.rows), this.sheetName || 'Sheet 1');
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
async function excelSheetWriter({ fileName, sheetName }) {
|
||||
return new ExcelSheetWriterStream({
|
||||
fileName,
|
||||
sheetName,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = excelSheetWriter;
|
||||
@@ -1,14 +1,10 @@
|
||||
const queryReader = require('./queryReader');
|
||||
const csvWriter = require('./csvWriter');
|
||||
const csvReader = require('./csvReader');
|
||||
const runScript = require('./runScript');
|
||||
const tableWriter = require('./tableWriter');
|
||||
const tableReader = require('./tableReader');
|
||||
const copyStream = require('./copyStream');
|
||||
const fakeObjectReader = require('./fakeObjectReader');
|
||||
const consoleObjectWriter = require('./consoleObjectWriter');
|
||||
const excelSheetReader = require('./excelSheetReader');
|
||||
const excelSheetWriter = require('./excelSheetWriter');
|
||||
const jsonLinesWriter = require('./jsonLinesWriter');
|
||||
const jsonLinesReader = require('./jsonLinesReader');
|
||||
const jslDataReader = require('./jslDataReader');
|
||||
@@ -16,16 +12,15 @@ const archiveWriter = require('./archiveWriter');
|
||||
const archiveReader = require('./archiveReader');
|
||||
const collectorWriter = require('./collectorWriter');
|
||||
const finalizer = require('./finalizer');
|
||||
const registerPlugins = require('./registerPlugins');
|
||||
const requirePlugin = require('./requirePlugin');
|
||||
|
||||
module.exports = {
|
||||
const dbgateApi = {
|
||||
queryReader,
|
||||
csvWriter,
|
||||
csvReader,
|
||||
runScript,
|
||||
tableWriter,
|
||||
tableReader,
|
||||
copyStream,
|
||||
excelSheetReader,
|
||||
jsonLinesWriter,
|
||||
jsonLinesReader,
|
||||
fakeObjectReader,
|
||||
@@ -34,6 +29,10 @@ module.exports = {
|
||||
archiveWriter,
|
||||
archiveReader,
|
||||
collectorWriter,
|
||||
excelSheetWriter,
|
||||
finalizer,
|
||||
registerPlugins,
|
||||
};
|
||||
|
||||
requirePlugin.initialize(dbgateApi);
|
||||
|
||||
module.exports = dbgateApi;
|
||||
|
||||
@@ -18,6 +18,7 @@ class ParseStream extends stream.Transform {
|
||||
}
|
||||
if (!this.limitRows || this.rowsWritten < this.limitRows) {
|
||||
this.push(obj);
|
||||
this.rowsWritten += 1;
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
const driverConnect = require('../utility/driverConnect');
|
||||
|
||||
const engines = require('dbgate-engines');
|
||||
const requireEngineDriver = require("../utility/requireEngineDriver");
|
||||
|
||||
async function queryReader({ connection, sql }) {
|
||||
console.log(`Reading query ${sql}`);
|
||||
|
||||
const driver = engines(connection);
|
||||
const pool = await driverConnect(driver, connection);
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await driver.connect(connection);
|
||||
console.log(`Connected.`);
|
||||
return await driver.readQuery(pool, sql);
|
||||
}
|
||||
|
||||
9
packages/api/src/shell/registerPlugins.js
Normal file
9
packages/api/src/shell/registerPlugins.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const requirePlugin = require('./requirePlugin');
|
||||
|
||||
function registerPlugins(...plugins) {
|
||||
for (const plugin of plugins) {
|
||||
requirePlugin(plugin.packageName, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = registerPlugins;
|
||||
37
packages/api/src/shell/requirePlugin.js
Normal file
37
packages/api/src/shell/requirePlugin.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const path = require('path');
|
||||
const { pluginsdir } = require('../utility/directories');
|
||||
|
||||
const loadedPlugins = {};
|
||||
|
||||
const dbgateEnv = {
|
||||
dbgateApi: null,
|
||||
};
|
||||
|
||||
function requirePlugin(packageName, requiredPlugin = null) {
|
||||
if (!packageName) throw new Error('Missing packageName in plugin');
|
||||
if (loadedPlugins[packageName]) return loadedPlugins[packageName];
|
||||
|
||||
if (requiredPlugin == null) {
|
||||
let module;
|
||||
const modulePath = path.join(pluginsdir(), packageName, 'dist', 'backend.js');
|
||||
console.log(`Loading module ${packageName} from ${modulePath}`);
|
||||
try {
|
||||
// @ts-ignore
|
||||
module = __non_webpack_require__(modulePath);
|
||||
} catch (err) {
|
||||
console.error('Failed load webpacked module', err);
|
||||
module = require(modulePath);
|
||||
}
|
||||
requiredPlugin = module.__esModule ? module.default : module;
|
||||
}
|
||||
loadedPlugins[packageName] = requiredPlugin;
|
||||
if (requiredPlugin.initialize) requiredPlugin.initialize(dbgateEnv);
|
||||
|
||||
return requiredPlugin;
|
||||
}
|
||||
|
||||
requirePlugin.initialize = (value) => {
|
||||
dbgateEnv.dbgateApi = value;
|
||||
};
|
||||
|
||||
module.exports = requirePlugin;
|
||||
@@ -1,11 +1,9 @@
|
||||
const { quoteFullName } = require('dbgate-tools');
|
||||
const driverConnect = require('../utility/driverConnect');
|
||||
|
||||
const engines = require('dbgate-engines');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
|
||||
async function tableReader({ connection, pureName, schemaName }) {
|
||||
const driver = engines(connection);
|
||||
const pool = await driverConnect(driver, connection);
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await driver.connect(connection);
|
||||
console.log(`Connected.`);
|
||||
|
||||
const fullName = { pureName, schemaName };
|
||||
@@ -14,11 +12,13 @@ async function tableReader({ connection, pureName, schemaName }) {
|
||||
const query = `select * from ${quoteFullName(driver.dialect, fullName)}`;
|
||||
if (table) {
|
||||
console.log(`Reading table ${table.pureName}`);
|
||||
// @ts-ignore
|
||||
return await driver.readQuery(pool, query, table);
|
||||
}
|
||||
const view = await driver.analyseSingleObject(pool, fullName, 'views');
|
||||
if (view) {
|
||||
console.log(`Reading view ${view.pureName}`);
|
||||
// @ts-ignore
|
||||
return await driver.readQuery(pool, query, view);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
const driverConnect = require('../utility/driverConnect');
|
||||
|
||||
const engines = require('dbgate-engines');
|
||||
const requireEngineDriver = require("../utility/requireEngineDriver");
|
||||
|
||||
async function tableWriter({ connection, schemaName, pureName, ...options }) {
|
||||
console.log(`Write table ${schemaName}.${pureName}`);
|
||||
|
||||
const driver = engines(connection);
|
||||
const pool = await driverConnect(driver, connection);
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await driver.connect(connection);
|
||||
console.log(`Connected.`);
|
||||
return await driver.writeTable(pool, { schemaName, pureName }, options);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ const dirFunc = (dirname, clean = false) => () => {
|
||||
const jsldir = dirFunc('jsl', true);
|
||||
const rundir = dirFunc('run', true);
|
||||
const uploadsdir = dirFunc('uploads', true);
|
||||
const pluginsdir = dirFunc('plugins');
|
||||
const archivedir = dirFunc('archive');
|
||||
|
||||
module.exports = {
|
||||
@@ -44,4 +45,5 @@ module.exports = {
|
||||
uploadsdir,
|
||||
archivedir,
|
||||
ensureDirectory,
|
||||
pluginsdir,
|
||||
};
|
||||
|
||||
64
packages/api/src/utility/downloadPackage.js
Normal file
64
packages/api/src/utility/downloadPackage.js
Normal file
@@ -0,0 +1,64 @@
|
||||
// const pacote = require('pacote');
|
||||
const axios = require('axios');
|
||||
// const tarballExtract = require('tarball-extract');
|
||||
const uuidv1 = require('uuid/v1');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const zlib = require('zlib');
|
||||
const tar = require('tar');
|
||||
const ncp = require('ncp').ncp;
|
||||
const { uploadsdir } = require('./directories');
|
||||
|
||||
function extractTarball(tmpFile, destination) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.createReadStream(tmpFile)
|
||||
.pipe(zlib.createGunzip())
|
||||
.pipe(tar.extract({ cwd: destination }))
|
||||
.on('error', (err) => reject(err))
|
||||
.on('end', () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
function saveStreamToFile(pipedStream, fileName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileStream = fs.createWriteStream(fileName);
|
||||
fileStream.on('close', () => resolve());
|
||||
pipedStream.pipe(fileStream);
|
||||
});
|
||||
}
|
||||
|
||||
function copyDirectory(source, target) {
|
||||
return new Promise((resolve, reject) => {
|
||||
ncp(source, target, (err) => {
|
||||
if (err) reject(err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function downloadPackage(packageName, directory) {
|
||||
// await pacote.extract(packageName, directory);
|
||||
const infoResp = await axios.default.get(`https://registry.npmjs.org/${packageName}`);
|
||||
|
||||
const { latest } = infoResp.data['dist-tags'] || {};
|
||||
if (!latest) return false;
|
||||
|
||||
const tarball = infoResp.data.versions[latest].dist.tarball;
|
||||
|
||||
const tmpFile = path.join(uploadsdir(), uuidv1() + '.tgz');
|
||||
console.log(`Downloading tarball ${tarball} into ${tmpFile}`);
|
||||
const tarballResp = await axios.default({
|
||||
method: 'get',
|
||||
url: tarball,
|
||||
responseType: 'stream',
|
||||
});
|
||||
await saveStreamToFile(tarballResp.data, tmpFile);
|
||||
const tmpDir = path.join(uploadsdir(), uuidv1());
|
||||
fs.mkdirSync(tmpDir);
|
||||
await extractTarball(tmpFile, tmpDir);
|
||||
await copyDirectory(path.join(tmpDir, 'package'), directory);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = downloadPackage;
|
||||
@@ -1,21 +0,0 @@
|
||||
const mssql = require('mssql');
|
||||
const mysql = require('mysql');
|
||||
const pg = require('pg');
|
||||
const pgQueryStream = require('pg-query-stream');
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
|
||||
const nativeModules = {
|
||||
mssql,
|
||||
mysql,
|
||||
pg,
|
||||
pgQueryStream,
|
||||
fs,
|
||||
stream,
|
||||
};
|
||||
|
||||
function driverConnect(driver, connection) {
|
||||
return driver.connect(nativeModules, connection);
|
||||
}
|
||||
|
||||
module.exports = driverConnect;
|
||||
24
packages/api/src/utility/requireEngineDriver.js
Normal file
24
packages/api/src/utility/requireEngineDriver.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const _ = require('lodash');
|
||||
const requirePlugin = require('../shell/requirePlugin');
|
||||
|
||||
|
||||
/** @returns {import('dbgate-types').EngineDriver} */
|
||||
function requireEngineDriver(connection) {
|
||||
let engine = null;
|
||||
if (_.isString(connection)) {
|
||||
engine = connection;
|
||||
} else if (_.isPlainObject(connection)) {
|
||||
engine = connection.engine;
|
||||
}
|
||||
if (!engine) {
|
||||
throw new Error('Could not get driver from connection');
|
||||
}
|
||||
if (engine.includes('@')) {
|
||||
const [shortName, packageName] = engine.split('@');
|
||||
const plugin = requirePlugin(packageName);
|
||||
return plugin.driver;
|
||||
}
|
||||
throw new Error(`Could not found engine driver ${engine}`);
|
||||
}
|
||||
|
||||
module.exports = requireEngineDriver;
|
||||
@@ -14,9 +14,9 @@ var config = {
|
||||
libraryTarget: 'commonjs2',
|
||||
},
|
||||
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
|
||||
// module: {
|
||||
// rules: [
|
||||
@@ -29,7 +29,7 @@ var config = {
|
||||
plugins: [
|
||||
new webpack.IgnorePlugin({
|
||||
checkResource(resource) {
|
||||
const lazyImports = ['pg-native', 'uws'];
|
||||
const lazyImports = ['uws'];
|
||||
if (!lazyImports.includes(resource)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -424,6 +424,7 @@ export abstract class GridDisplay {
|
||||
}
|
||||
|
||||
getPageQuery(offset: number, count: number) {
|
||||
if (!this.driver) return null;
|
||||
const select = this.createSelect();
|
||||
if (!select) return null;
|
||||
if (this.driver.dialect.rangeSelect) select.range = { offset: offset, limit: count };
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# dbgate-engines
|
||||
|
||||
JavaScript library implementing MySQL, MS SQL and PostgreSQL operations. Server as abstraction layer for other DbGate packages, which could be database-engine independend. It can be used both on frontent (in browser) and on backend (in nodejs), but connection to real database is allowed only on backend.
|
||||
|
||||
## Installation
|
||||
|
||||
yarn add dbgate-engines
|
||||
|
||||
## Usage
|
||||
```javascript
|
||||
const engines = require('dbgate-engines');
|
||||
// driver supports operations of EngineDriver listed belowe
|
||||
const driver = engine('mysql');
|
||||
```
|
||||
|
||||
In most cases, you don't use driver methods directly, but you pass driver instance into other dbgate packages.
|
||||
|
||||
## Driver definition
|
||||
|
||||
|
||||
```typescript
|
||||
export interface EngineDriver {
|
||||
// works on both frontend and backend
|
||||
engine: string;
|
||||
dialect: SqlDialect;
|
||||
createDumper(): SqlDumper;
|
||||
|
||||
// works only on backend
|
||||
connect(nativeModules, { server, port, user, password, database }): any;
|
||||
query(pool: any, sql: string): Promise<QueryResult>;
|
||||
stream(pool: any, sql: string, options: StreamOptions);
|
||||
readQuery(pool: any, sql: string, structure?: TableInfo): Promise<stream.Readable>;
|
||||
writeTable(pool: any, name: NamedObjectInfo, options: WriteTableOptions): Promise<stream.Writeable>;
|
||||
analyseSingleObject(
|
||||
pool: any,
|
||||
name: NamedObjectInfo,
|
||||
objectTypeField: keyof DatabaseInfo
|
||||
): Promise<TableInfo | ViewInfo | ProcedureInfo | FunctionInfo | TriggerInfo>;
|
||||
analyseSingleTable(pool: any, name: NamedObjectInfo): Promise<TableInfo>;
|
||||
getVersion(pool: any): Promise<{ version: string }>;
|
||||
listDatabases(
|
||||
pool: any
|
||||
): Promise<
|
||||
{
|
||||
name: string;
|
||||
}[]
|
||||
>;
|
||||
analyseFull(pool: any): Promise<DatabaseInfo>;
|
||||
analyseIncremental(pool: any, structure: DatabaseInfo): Promise<DatabaseInfo>;
|
||||
}
|
||||
|
||||
```
|
||||
7
packages/engines/index.d.ts
vendored
7
packages/engines/index.d.ts
vendored
@@ -1,7 +0,0 @@
|
||||
import types from "dbgate-types";
|
||||
|
||||
declare function getDriver(
|
||||
connection: string | { engine: string }
|
||||
): types.EngineDriver;
|
||||
|
||||
export = getDriver;
|
||||
@@ -1,24 +0,0 @@
|
||||
const _ = require("lodash");
|
||||
const mssql = require("./mssql");
|
||||
const mysql = require("./mysql");
|
||||
const postgres = require("./postgres");
|
||||
|
||||
const drivers = {
|
||||
mssql,
|
||||
mysql,
|
||||
postgres
|
||||
};
|
||||
|
||||
function getDriver(connection) {
|
||||
if (_.isString(connection)) {
|
||||
return drivers[connection];
|
||||
}
|
||||
if (_.isPlainObject(connection)) {
|
||||
const { engine } = connection;
|
||||
if (engine) {
|
||||
return drivers[engine];
|
||||
}
|
||||
}
|
||||
throw new Error(`Cannot extract engine from ${connection}`);
|
||||
}
|
||||
module.exports = getDriver;
|
||||
@@ -1,212 +0,0 @@
|
||||
const fp = require('lodash/fp');
|
||||
const _ = require('lodash');
|
||||
const sql = require('./sql');
|
||||
|
||||
const DatabaseAnalyser = require('../default/DatabaseAnalyser');
|
||||
const { filter } = require('lodash');
|
||||
const { isTypeString, isTypeNumeric } = require('dbgate-tools');
|
||||
|
||||
function objectTypeToField(type) {
|
||||
switch (type.trim()) {
|
||||
case 'U':
|
||||
return 'tables';
|
||||
case 'V':
|
||||
return 'views';
|
||||
case 'P':
|
||||
return 'procedures';
|
||||
case 'IF':
|
||||
case 'FN':
|
||||
case 'TF':
|
||||
return 'functions';
|
||||
case 'TR':
|
||||
return 'triggers';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getColumnInfo({
|
||||
isNullable,
|
||||
isIdentity,
|
||||
columnName,
|
||||
dataType,
|
||||
charMaxLength,
|
||||
numericPrecision,
|
||||
numericScale,
|
||||
}) {
|
||||
let fullDataType = dataType;
|
||||
if (charMaxLength && isTypeString(dataType)) fullDataType = `${dataType}(${charMaxLength})`;
|
||||
if (numericPrecision && numericScale && isTypeNumeric(dataType))
|
||||
fullDataType = `${dataType}(${numericPrecision},${numericScale})`;
|
||||
return {
|
||||
columnName,
|
||||
dataType: fullDataType,
|
||||
notNull: !isNullable,
|
||||
autoIncrement: !!isIdentity,
|
||||
};
|
||||
}
|
||||
|
||||
class MsSqlAnalyser extends DatabaseAnalyser {
|
||||
constructor(pool, driver) {
|
||||
super(pool, driver);
|
||||
this.singleObjectId = null;
|
||||
}
|
||||
|
||||
createQuery(resFileName, typeFields) {
|
||||
let res = sql[resFileName];
|
||||
if (this.singleObjectFilter) {
|
||||
const { typeField } = this.singleObjectFilter;
|
||||
if (!this.singleObjectId) return null;
|
||||
if (!typeFields || !typeFields.includes(typeField)) return null;
|
||||
return res.replace('=[OBJECT_ID_CONDITION]', ` = ${this.singleObjectId}`);
|
||||
}
|
||||
if (!this.modifications || !typeFields || this.modifications.length == 0) {
|
||||
res = res.replace('=[OBJECT_ID_CONDITION]', ' is not null');
|
||||
} else {
|
||||
const filterIds = this.modifications
|
||||
.filter((x) => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change'))
|
||||
.map((x) => x.objectId);
|
||||
if (filterIds.length == 0) {
|
||||
res = res.replace('=[OBJECT_ID_CONDITION]', ' = 0');
|
||||
} else {
|
||||
res = res.replace('=[OBJECT_ID_CONDITION]', ` in (${filterIds.join(',')})`);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async getSingleObjectId() {
|
||||
if (this.singleObjectFilter) {
|
||||
const { schemaName, pureName, typeField } = this.singleObjectFilter;
|
||||
const fullName = schemaName ? `[${schemaName}].[${pureName}]` : pureName;
|
||||
const resId = await this.driver.query(this.pool, `SELECT OBJECT_ID('${fullName}') AS id`);
|
||||
this.singleObjectId = resId.rows[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
async _runAnalysis() {
|
||||
await this.getSingleObjectId();
|
||||
const tablesRows = await this.driver.query(this.pool, this.createQuery('tables', ['tables']));
|
||||
const columnsRows = await this.driver.query(this.pool, this.createQuery('columns', ['tables']));
|
||||
const pkColumnsRows = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables']));
|
||||
const fkColumnsRows = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables']));
|
||||
const schemaRows = await this.driver.query(this.pool, this.createQuery('getSchemas'));
|
||||
|
||||
const schemas = schemaRows.rows;
|
||||
|
||||
const sqlCodeRows = await this.driver.query(
|
||||
this.pool,
|
||||
this.createQuery('loadSqlCode', ['views', 'procedures', 'functions', 'triggers'])
|
||||
);
|
||||
const getCreateSql = (row) =>
|
||||
sqlCodeRows.rows
|
||||
.filter((x) => x.pureName == row.pureName && x.schemaName == row.schemaName)
|
||||
.map((x) => x.codeText)
|
||||
.join('');
|
||||
const viewsRows = await this.driver.query(this.pool, this.createQuery('views', ['views']));
|
||||
const programmableRows = await this.driver.query(
|
||||
this.pool,
|
||||
this.createQuery('programmables', ['procedures', 'functions'])
|
||||
);
|
||||
const viewColumnRows = await this.driver.query(this.pool, this.createQuery('viewColumns', ['views']));
|
||||
|
||||
const tables = tablesRows.rows.map((row) => ({
|
||||
...row,
|
||||
columns: columnsRows.rows.filter((col) => col.objectId == row.objectId).map(getColumnInfo),
|
||||
primaryKey: DatabaseAnalyser.extractPrimaryKeys(row, pkColumnsRows.rows),
|
||||
foreignKeys: DatabaseAnalyser.extractForeignKeys(row, fkColumnsRows.rows),
|
||||
}));
|
||||
|
||||
const views = viewsRows.rows.map((row) => ({
|
||||
...row,
|
||||
createSql: getCreateSql(row),
|
||||
columns: viewColumnRows.rows.filter((col) => col.objectId == row.objectId).map(getColumnInfo),
|
||||
}));
|
||||
|
||||
const procedures = programmableRows.rows
|
||||
.filter((x) => x.sqlObjectType.trim() == 'P')
|
||||
.map((row) => ({
|
||||
...row,
|
||||
createSql: getCreateSql(row),
|
||||
}));
|
||||
|
||||
const functions = programmableRows.rows
|
||||
.filter((x) => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim()))
|
||||
.map((row) => ({
|
||||
...row,
|
||||
createSql: getCreateSql(row),
|
||||
}));
|
||||
|
||||
return this.mergeAnalyseResult(
|
||||
{
|
||||
tables,
|
||||
views,
|
||||
procedures,
|
||||
functions,
|
||||
schemas,
|
||||
},
|
||||
(x) => x.objectId
|
||||
);
|
||||
}
|
||||
|
||||
getDeletedObjectsForField(idArray, objectTypeField) {
|
||||
return this.structure[objectTypeField]
|
||||
.filter((x) => !idArray.includes(x.objectId))
|
||||
.map((x) => ({
|
||||
oldName: _.pick(x, ['schemaName', 'pureName']),
|
||||
objectId: x.objectId,
|
||||
action: 'remove',
|
||||
objectTypeField,
|
||||
}));
|
||||
}
|
||||
|
||||
getDeletedObjects(idArray) {
|
||||
return [
|
||||
...this.getDeletedObjectsForField(idArray, 'tables'),
|
||||
...this.getDeletedObjectsForField(idArray, 'views'),
|
||||
...this.getDeletedObjectsForField(idArray, 'procedures'),
|
||||
...this.getDeletedObjectsForField(idArray, 'functions'),
|
||||
...this.getDeletedObjectsForField(idArray, 'triggers'),
|
||||
];
|
||||
}
|
||||
|
||||
async getModifications() {
|
||||
const modificationsQueryData = await this.driver.query(this.pool, this.createQuery('modifications'));
|
||||
// console.log('MOD - SRC', modifications);
|
||||
// console.log(
|
||||
// 'MODs',
|
||||
// this.structure.tables.map((x) => x.modifyDate)
|
||||
// );
|
||||
const modifications = modificationsQueryData.rows.map((x) => {
|
||||
const { type, objectId, modifyDate, schemaName, pureName } = x;
|
||||
const field = objectTypeToField(type);
|
||||
if (!this.structure[field]) return null;
|
||||
// @ts-ignore
|
||||
const obj = this.structure[field].find((x) => x.objectId == objectId);
|
||||
|
||||
// object not modified
|
||||
if (obj && Math.abs(new Date(modifyDate).getTime() - new Date(obj.modifyDate).getTime()) < 1000) return null;
|
||||
|
||||
/** @type {import('dbgate-types').DatabaseModification} */
|
||||
const action = obj
|
||||
? {
|
||||
newName: { schemaName, pureName },
|
||||
oldName: _.pick(obj, ['schemaName', 'pureName']),
|
||||
action: 'change',
|
||||
objectTypeField: field,
|
||||
objectId,
|
||||
}
|
||||
: {
|
||||
newName: { schemaName, pureName },
|
||||
action: 'add',
|
||||
objectTypeField: field,
|
||||
objectId,
|
||||
};
|
||||
return action;
|
||||
});
|
||||
|
||||
return [..._.compact(modifications), ...this.getDeletedObjects(modificationsQueryData.rows.map((x) => x.objectId))];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MsSqlAnalyser;
|
||||
@@ -1,54 +0,0 @@
|
||||
const SqlDumper = require('../default/SqlDumper');
|
||||
|
||||
class MsSqlDumper extends SqlDumper {
|
||||
autoIncrement() {
|
||||
this.put(' ^identity');
|
||||
}
|
||||
|
||||
putStringValue(value) {
|
||||
if (/[^\u0000-\u00ff]/.test(value)) {
|
||||
this.putRaw('N');
|
||||
}
|
||||
super.putStringValue(value);
|
||||
}
|
||||
|
||||
allowIdentityInsert(table, allow) {
|
||||
this.putCmd('^set ^identity_insert %f %k;&n', table, allow ? 'on' : 'off');
|
||||
}
|
||||
|
||||
/** @param type {import('dbgate-types').TransformType} */
|
||||
transform(type, dumpExpr) {
|
||||
switch (type) {
|
||||
case 'GROUP:YEAR':
|
||||
case 'YEAR':
|
||||
this.put('^datepart(^year, %c)', dumpExpr);
|
||||
break;
|
||||
case 'MONTH':
|
||||
this.put('^datepart(^month, %c)', dumpExpr);
|
||||
break;
|
||||
case 'DAY':
|
||||
this.put('^datepart(^day, %c)', dumpExpr);
|
||||
break;
|
||||
case 'GROUP:MONTH':
|
||||
this.put(
|
||||
"^convert(^varchar(100), ^datepart(^year, %c)) + '-' + ^convert(^varchar(100), ^datepart(^month, %c))",
|
||||
dumpExpr,
|
||||
dumpExpr
|
||||
);
|
||||
break;
|
||||
case 'GROUP:DAY':
|
||||
this.put(
|
||||
"^convert(^varchar(100), ^datepart(^year, %c)) + '-' + ^convert(^varchar(100), ^datepart(^month, %c))+'-' + ^convert(^varchar(100), ^datepart(^day, %c))",
|
||||
dumpExpr,
|
||||
dumpExpr,
|
||||
dumpExpr
|
||||
);
|
||||
break;
|
||||
default:
|
||||
dumpExpr();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MsSqlDumper;
|
||||
@@ -1,48 +0,0 @@
|
||||
const createBulkInsertStreamBase = require('../default/createBulkInsertStreamBase');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('dbgate-types').EngineDriver} driver
|
||||
*/
|
||||
function createBulkInsertStream(driver, mssql, stream, pool, name, options) {
|
||||
const writable = createBulkInsertStreamBase(driver, stream, pool, name, options);
|
||||
|
||||
const fullName = name.schemaName ? `[${name.schemaName}].[${name.pureName}]` : name.pureName;
|
||||
|
||||
writable.send = async () => {
|
||||
if (!writable.templateColumns) {
|
||||
const fullNameQuoted = name.schemaName
|
||||
? `${driver.dialect.quoteIdentifier(name.schemaName)}.${driver.dialect.quoteIdentifier(name.pureName)}`
|
||||
: driver.dialect.quoteIdentifier(name.pureName);
|
||||
|
||||
const respTemplate = await pool.request().query(`SELECT * FROM ${fullNameQuoted} WHERE 1=0`);
|
||||
writable.templateColumns = respTemplate.recordset.toTable().columns;
|
||||
}
|
||||
|
||||
const rows = writable.buffer;
|
||||
writable.buffer = [];
|
||||
const table = new mssql.Table(fullName);
|
||||
// table.create = options.createIfNotExists;
|
||||
for (const column of this.columnNames) {
|
||||
const tcol = writable.templateColumns.find((x) => x.name == column);
|
||||
// console.log('TCOL', tcol);
|
||||
// console.log('TYPE', tcol.type, mssql.Int);
|
||||
// table.columns.add(column, tcol ? tcol.type : mssql.NVarChar(mssql.MAX));
|
||||
table.columns.add(column, tcol ? tcol.type : mssql.NVarChar(mssql.MAX), {
|
||||
nullable: tcol.nullable,
|
||||
length: tcol.length,
|
||||
precision: tcol.precision,
|
||||
scale: tcol.scale,
|
||||
});
|
||||
}
|
||||
for (const row of rows) {
|
||||
table.rows.add(...this.columnNames.map((col) => row[col]));
|
||||
}
|
||||
const request = pool.request();
|
||||
await request.bulk(table);
|
||||
};
|
||||
|
||||
return writable;
|
||||
}
|
||||
|
||||
module.exports = createBulkInsertStream;
|
||||
@@ -1,210 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
const MsSqlAnalyser = require('./MsSqlAnalyser');
|
||||
const MsSqlDumper = require('./MsSqlDumper');
|
||||
const createBulkInsertStream = require('./createBulkInsertStream');
|
||||
const driverBase = require('../default/driverBase');
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
const dialect = {
|
||||
limitSelect: true,
|
||||
rangeSelect: true,
|
||||
offsetFetchRangeSyntax: true,
|
||||
stringEscapeChar: "'",
|
||||
fallbackDataType: 'nvarchar(max)',
|
||||
quoteIdentifier(s) {
|
||||
return `[${s}]`;
|
||||
},
|
||||
};
|
||||
|
||||
function extractColumns(columns) {
|
||||
const mapper = {};
|
||||
const res = _.sortBy(_.values(columns), 'index').map((col) => ({
|
||||
...col,
|
||||
columnName: col.name,
|
||||
notNull: !col.nullable,
|
||||
}));
|
||||
|
||||
const generateName = () => {
|
||||
let index = 1;
|
||||
while (res.find((x) => x.columnName == `col${index}`)) index += 1;
|
||||
return `col${index}`;
|
||||
};
|
||||
|
||||
// const groups = _.groupBy(res, 'columnName');
|
||||
// for (const colname of _.keys(groups)) {
|
||||
// if (groups[colname].length == 1) continue;
|
||||
// mapper[colname] = [];
|
||||
// for (const col of groups[colname]) {
|
||||
// col.columnName = generateName();
|
||||
// mapper[colname].push(colname);
|
||||
// }
|
||||
// }
|
||||
|
||||
for (const col of res) {
|
||||
if (!col.columnName) {
|
||||
const newName = generateName();
|
||||
mapper[col.columnName] = newName;
|
||||
col.columnName = newName;
|
||||
}
|
||||
}
|
||||
|
||||
return [res, mapper];
|
||||
}
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
analyserClass: MsSqlAnalyser,
|
||||
dumperClass: MsSqlDumper,
|
||||
async connect(nativeModules, { server, port, user, password, database }) {
|
||||
const pool = new nativeModules.mssql.ConnectionPool({
|
||||
server,
|
||||
port,
|
||||
user,
|
||||
password,
|
||||
database,
|
||||
requestTimeout: 1000 * 3600,
|
||||
options: {
|
||||
enableArithAbort: true,
|
||||
},
|
||||
});
|
||||
await pool.connect();
|
||||
pool._nativeModules = nativeModules;
|
||||
return pool;
|
||||
},
|
||||
// @ts-ignore
|
||||
async query(pool, sql) {
|
||||
if (sql == null) {
|
||||
return {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
}
|
||||
const resp = await pool.request().query(sql);
|
||||
// console.log(Object.keys(resp.recordset));
|
||||
// console.log(resp);
|
||||
const res = {};
|
||||
|
||||
if (resp.recordset) {
|
||||
const [columns] = extractColumns(resp.recordset.columns);
|
||||
res.columns = columns;
|
||||
res.rows = resp.recordset;
|
||||
}
|
||||
if (resp.rowsAffected) {
|
||||
res.rowsAffected = _.sum(resp.rowsAffected);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
async stream(pool, sql, options) {
|
||||
const request = await pool.request();
|
||||
let currentMapper = null;
|
||||
|
||||
const handleInfo = (info) => {
|
||||
const { message, lineNumber, procName } = info;
|
||||
options.info({
|
||||
message,
|
||||
line: lineNumber,
|
||||
procedure: procName,
|
||||
time: new Date(),
|
||||
severity: 'info',
|
||||
});
|
||||
};
|
||||
|
||||
const handleDone = (result) => {
|
||||
// console.log('RESULT', result);
|
||||
options.done(result);
|
||||
};
|
||||
|
||||
const handleRow = (row) => {
|
||||
// if (currentMapper) {
|
||||
// for (const colname of _.keys(currentMapper)) {
|
||||
// let index = 0;
|
||||
// for (const newcolname of currentMapper[colname]) {
|
||||
// row[newcolname] = row[colname][index];
|
||||
// index += 1;
|
||||
// }
|
||||
// delete row[colname];
|
||||
// }
|
||||
// }
|
||||
if (currentMapper) {
|
||||
row = { ...row };
|
||||
for (const colname of _.keys(currentMapper)) {
|
||||
const newcolname = currentMapper[colname];
|
||||
row[newcolname] = row[colname];
|
||||
if (_.isArray(row[newcolname])) row[newcolname] = row[newcolname].join(',');
|
||||
delete row[colname];
|
||||
}
|
||||
}
|
||||
|
||||
options.row(row);
|
||||
};
|
||||
|
||||
const handleRecordset = (columns) => {
|
||||
const [extractedColumns, mapper] = extractColumns(columns);
|
||||
currentMapper = mapper;
|
||||
options.recordset(extractedColumns);
|
||||
};
|
||||
|
||||
const handleError = (error) => {
|
||||
const { message, lineNumber, procName } = error;
|
||||
options.info({
|
||||
message,
|
||||
line: lineNumber,
|
||||
procedure: procName,
|
||||
time: new Date(),
|
||||
severity: 'error',
|
||||
});
|
||||
};
|
||||
|
||||
request.stream = true;
|
||||
request.on('recordset', handleRecordset);
|
||||
request.on('row', handleRow);
|
||||
request.on('error', handleError);
|
||||
request.on('done', handleDone);
|
||||
request.on('info', handleInfo);
|
||||
request.query(sql);
|
||||
|
||||
return request;
|
||||
},
|
||||
async readQuery(pool, sql, structure) {
|
||||
const request = await pool.request();
|
||||
const { stream } = pool._nativeModules;
|
||||
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
});
|
||||
|
||||
request.stream = true;
|
||||
request.on('recordset', (driverColumns) => {
|
||||
const [columns, mapper] = extractColumns(driverColumns);
|
||||
pass.write(structure || { columns });
|
||||
});
|
||||
request.on('row', (row) => pass.write(row));
|
||||
request.on('error', (err) => {
|
||||
console.error(err);
|
||||
pass.end();
|
||||
});
|
||||
request.on('done', () => pass.end());
|
||||
|
||||
request.query(sql);
|
||||
|
||||
return pass;
|
||||
},
|
||||
async writeTable(pool, name, options) {
|
||||
const { stream, mssql } = pool._nativeModules;
|
||||
return createBulkInsertStream(this, mssql, stream, pool, name, options);
|
||||
},
|
||||
async getVersion(pool) {
|
||||
const { version } = (await this.query(pool, 'SELECT @@VERSION AS version')).rows[0];
|
||||
return { version };
|
||||
},
|
||||
async listDatabases(pool) {
|
||||
const { rows } = await this.query(pool, 'SELECT name FROM sys.databases order by name');
|
||||
return rows;
|
||||
},
|
||||
dialect,
|
||||
engine: 'mssql',
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports = `
|
||||
select c.name as columnName, t.name as dataType, c.object_id as objectId, c.is_identity as isIdentity,
|
||||
c.max_length as maxLength, c.precision, c.scale, c.is_nullable as isNullable,
|
||||
col.CHARACTER_MAXIMUM_LENGTH as charMaxLength,
|
||||
d.definition as defaultValue, d.name as defaultConstraint,
|
||||
m.definition as computedExpression, m.is_persisted as isPersisted, c.column_id as columnId,
|
||||
col.NUMERIC_PRECISION as numericPrecision,
|
||||
col.NUMERIC_SCALE as numericScale,
|
||||
-- TODO only if version >= 2008
|
||||
c.is_sparse as isSparse
|
||||
from sys.columns c
|
||||
inner join sys.types t on c.system_type_id = t.system_type_id and c.user_type_id = t.user_type_id
|
||||
inner join sys.objects o on c.object_id = o.object_id
|
||||
INNER JOIN sys.schemas u ON u.schema_id=o.schema_id
|
||||
INNER JOIN INFORMATION_SCHEMA.COLUMNS col ON col.TABLE_NAME = o.name AND col.TABLE_SCHEMA = u.name and col.COLUMN_NAME = c.name
|
||||
left join sys.default_constraints d on c.default_object_id = d.object_id
|
||||
left join sys.computed_columns m on m.object_id = c.object_id and m.column_id = c.column_id
|
||||
where o.type = 'U' and o.object_id =[OBJECT_ID_CONDITION]
|
||||
order by c.column_id
|
||||
`;
|
||||
@@ -1,40 +0,0 @@
|
||||
module.exports = `
|
||||
SELECT
|
||||
schemaName = FK.TABLE_SCHEMA,
|
||||
pureName = FK.TABLE_NAME,
|
||||
columnName = CU.COLUMN_NAME,
|
||||
|
||||
refSchemaName = ISNULL(IXS.name, PK.TABLE_SCHEMA),
|
||||
refTableName = ISNULL(IXT.name, PK.TABLE_NAME),
|
||||
refColumnName = IXCC.name,
|
||||
|
||||
constraintName = C.CONSTRAINT_NAME,
|
||||
updateAction = rc.UPDATE_RULE,
|
||||
deleteAction = rc.DELETE_RULE,
|
||||
|
||||
objectId = o.object_id
|
||||
|
||||
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C
|
||||
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
|
||||
|
||||
LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
|
||||
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME
|
||||
--LEFT JOIN (
|
||||
--SELECT i1.TABLE_NAME, i2.COLUMN_NAME
|
||||
--FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1
|
||||
--INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME
|
||||
--WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||
--) PT ON PT.TABLE_NAME = PK.TABLE_NAME
|
||||
INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc ON FK.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
||||
|
||||
LEFT JOIN sys.indexes IX ON IX.name = C.UNIQUE_CONSTRAINT_NAME
|
||||
LEFT JOIN sys.objects IXT ON IXT.object_id = IX.object_id
|
||||
LEFT JOIN sys.index_columns IXC ON IX.index_id = IXC.index_id and IX.object_id = IXC.object_id
|
||||
LEFT JOIN sys.columns IXCC ON IXCC.column_id = IXC.column_id AND IXCC.object_id = IXC.object_id
|
||||
LEFT JOIN sys.schemas IXS ON IXT.schema_id = IXS.schema_id
|
||||
|
||||
inner join sys.objects o on FK.TABLE_NAME = o.name
|
||||
inner join sys.schemas s on o.schema_id = s.schema_id and FK.TABLE_SCHEMA = s.name
|
||||
|
||||
where o.object_id =[OBJECT_ID_CONDITION]
|
||||
`;
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = `select schema_id as objectId, name as schemaName from sys.schemas`;
|
||||
@@ -1,23 +0,0 @@
|
||||
const columns = require('./columns');
|
||||
const foreignKeys = require('./foreignKeys');
|
||||
const primaryKeys = require('./primaryKeys');
|
||||
const tables = require('./tables');
|
||||
const modifications = require('./modifications');
|
||||
const loadSqlCode = require('./loadSqlCode');
|
||||
const views = require('./views');
|
||||
const programmables = require('./programmables');
|
||||
const viewColumns = require('./viewColumns');
|
||||
const getSchemas = require('./getSchemas');
|
||||
|
||||
module.exports = {
|
||||
columns,
|
||||
tables,
|
||||
foreignKeys,
|
||||
primaryKeys,
|
||||
modifications,
|
||||
loadSqlCode,
|
||||
views,
|
||||
programmables,
|
||||
viewColumns,
|
||||
getSchemas,
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = `
|
||||
select s.name as pureName, u.name as schemaName, c.text AS codeText
|
||||
from sys.objects s
|
||||
inner join sys.syscomments c on s.object_id = c.id
|
||||
inner join sys.schemas u on u.schema_id = s.schema_id
|
||||
where (s.object_id =[OBJECT_ID_CONDITION])
|
||||
order by u.name, s.name, c.colid
|
||||
`;
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = `
|
||||
select o.object_id as objectId, o.modify_date as modifyDate, o.type, o.name as pureName, s.name as schemaName
|
||||
from sys.objects o
|
||||
inner join sys.schemas s on o.schema_id = s.schema_id
|
||||
where o.type in ('U', 'V', 'P', 'IF', 'FN', 'TF') -- , 'TR' - triggers disabled
|
||||
`;
|
||||
@@ -1,14 +0,0 @@
|
||||
module.exports = `
|
||||
select o.object_id, pureName = t.Table_Name, schemaName = t.Table_Schema, columnName = c.Column_Name, constraintName=t.constraint_name from
|
||||
INFORMATION_SCHEMA.TABLE_CONSTRAINTS t,
|
||||
sys.objects o,
|
||||
sys.schemas s,
|
||||
INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE c
|
||||
where
|
||||
c.Constraint_Name = t.Constraint_Name
|
||||
and t.table_name = o.name
|
||||
and o.schema_id = s.schema_id and t.Table_Schema = s.name
|
||||
and c.Table_Name = t.Table_Name
|
||||
and Constraint_Type = 'PRIMARY KEY'
|
||||
and o.object_id =[OBJECT_ID_CONDITION]
|
||||
`;
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = `
|
||||
select o.name as pureName, s.name as schemaName, o.object_id as objectId, o.create_date as createDate, o.modify_date as modifyDate, o.type as sqlObjectType
|
||||
from sys.objects o
|
||||
inner join sys.schemas s on o.schema_id = s.schema_id
|
||||
where o.type in ('P', 'IF', 'FN', 'TF') and o.object_id =[OBJECT_ID_CONDITION]
|
||||
`;
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
o.name as pureName, s.name as schemaName, o.object_id as objectId,
|
||||
o.create_date as createDate, o.modify_date as modifyDate
|
||||
from sys.tables o
|
||||
inner join sys.schemas s on o.schema_id = s.schema_id
|
||||
where o.object_id =[OBJECT_ID_CONDITION]
|
||||
`;
|
||||
@@ -1,18 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
o.object_id AS objectId,
|
||||
col.TABLE_SCHEMA as schemaName,
|
||||
col.TABLE_NAME as pureName,
|
||||
col.COLUMN_NAME as columnName,
|
||||
col.IS_NULLABLE as isNullable,
|
||||
col.DATA_TYPE as dataType,
|
||||
col.CHARACTER_MAXIMUM_LENGTH as charMaxLength,
|
||||
col.NUMERIC_PRECISION as precision,
|
||||
col.NUMERIC_SCALE as scale,
|
||||
col.COLUMN_DEFAULT
|
||||
FROM sys.objects o
|
||||
INNER JOIN sys.schemas u ON u.schema_id=o.schema_id
|
||||
INNER JOIN INFORMATION_SCHEMA.COLUMNS col ON col.TABLE_NAME = o.name AND col.TABLE_SCHEMA = u.name
|
||||
WHERE o.type in ('V') and o.object_id =[OBJECT_ID_CONDITION]
|
||||
order by col.ORDINAL_POSITION
|
||||
`;
|
||||
@@ -1,10 +0,0 @@
|
||||
module.exports = `
|
||||
SELECT
|
||||
o.name as pureName,
|
||||
u.name as schemaName,
|
||||
o.object_id as objectId,
|
||||
o.create_date as createDate,
|
||||
o.modify_date as modifyDate
|
||||
FROM sys.objects o INNER JOIN sys.schemas u ON u.schema_id=o.schema_id
|
||||
WHERE type in ('V') and o.object_id =[OBJECT_ID_CONDITION]
|
||||
`;
|
||||
@@ -1,214 +0,0 @@
|
||||
const fp = require('lodash/fp');
|
||||
const _ = require('lodash');
|
||||
const sql = require('./sql');
|
||||
|
||||
const DatabaseAnalayser = require('../default/DatabaseAnalyser');
|
||||
const { isTypeString, isTypeNumeric } = require('dbgate-tools');
|
||||
const { rangeStep } = require('lodash/fp');
|
||||
|
||||
function getColumnInfo({
|
||||
isNullable,
|
||||
extra,
|
||||
columnName,
|
||||
dataType,
|
||||
charMaxLength,
|
||||
numericPrecision,
|
||||
numericScale,
|
||||
defaultValue,
|
||||
}) {
|
||||
let fullDataType = dataType;
|
||||
if (charMaxLength && isTypeString(dataType)) fullDataType = `${dataType}(${charMaxLength})`;
|
||||
if (numericPrecision && numericScale && isTypeNumeric(dataType))
|
||||
fullDataType = `${dataType}(${numericPrecision},${numericScale})`;
|
||||
return {
|
||||
notNull: !isNullable || isNullable == 'NO' || isNullable == 'no',
|
||||
autoIncrement: extra && extra.toLowerCase().includes('auto_increment'),
|
||||
columnName,
|
||||
dataType: fullDataType,
|
||||
defaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
function objectTypeToField(type) {
|
||||
if (type == 'VIEW') return 'views';
|
||||
if (type == 'BASE TABLE') return 'tables';
|
||||
return null;
|
||||
}
|
||||
|
||||
class MySqlAnalyser extends DatabaseAnalayser {
|
||||
constructor(pool, driver) {
|
||||
super(pool, driver);
|
||||
}
|
||||
|
||||
createQuery(resFileName, typeFields) {
|
||||
let res = sql[resFileName];
|
||||
if (this.singleObjectFilter) {
|
||||
const { typeField, pureName } = this.singleObjectFilter;
|
||||
if (!typeFields || !typeFields.includes(typeField)) return null;
|
||||
res = res.replace('=[OBJECT_NAME_CONDITION]', ` = '${pureName}'`).replace('#DATABASE#', this.pool._database_name);
|
||||
return res;
|
||||
}
|
||||
if (!this.modifications || !typeFields || this.modifications.length == 0) {
|
||||
res = res.replace('=[OBJECT_NAME_CONDITION]', ' is not null');
|
||||
} else {
|
||||
const filterNames = this.modifications
|
||||
.filter((x) => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change'))
|
||||
.map((x) => x.newName && x.newName.pureName)
|
||||
.filter(Boolean);
|
||||
if (filterNames.length == 0) {
|
||||
res = res.replace('=[OBJECT_NAME_CONDITION]', ' IS NULL');
|
||||
} else {
|
||||
res = res.replace('=[OBJECT_NAME_CONDITION]', ` in (${filterNames.map((x) => `'${x}'`).join(',')})`);
|
||||
}
|
||||
}
|
||||
res = res.replace('#DATABASE#', this.pool._database_name);
|
||||
return res;
|
||||
}
|
||||
|
||||
getRequestedViewNames(allViewNames) {
|
||||
if (this.singleObjectFilter) {
|
||||
const { typeField, pureName } = this.singleObjectFilter;
|
||||
if (typeField == 'views') return [pureName];
|
||||
}
|
||||
if (this.modifications) {
|
||||
return this.modifications.filter((x) => x.objectTypeField == 'views').map((x) => x.newName.pureName);
|
||||
}
|
||||
return allViewNames;
|
||||
}
|
||||
|
||||
async getViewTexts(allViewNames) {
|
||||
const res = {};
|
||||
for (const viewName of this.getRequestedViewNames(allViewNames)) {
|
||||
const resp = await this.driver.query(this.pool, `SHOW CREATE VIEW \`${viewName}\``);
|
||||
res[viewName] = resp.rows[0]['Create View'];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async _runAnalysis() {
|
||||
const tables = await this.driver.query(this.pool, this.createQuery('tables', ['tables']));
|
||||
const columns = await this.driver.query(this.pool, this.createQuery('columns', ['tables', 'views']));
|
||||
const pkColumns = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables']));
|
||||
const fkColumns = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables']));
|
||||
const views = await this.driver.query(this.pool, this.createQuery('views', ['views']));
|
||||
const programmables = await this.driver.query(
|
||||
this.pool,
|
||||
this.createQuery('programmables', ['procedures', 'functions'])
|
||||
);
|
||||
|
||||
const viewTexts = await this.getViewTexts(views.rows.map((x) => x.pureName));
|
||||
|
||||
return this.mergeAnalyseResult(
|
||||
{
|
||||
tables: tables.rows.map((table) => ({
|
||||
...table,
|
||||
columns: columns.rows.filter((col) => col.pureName == table.pureName).map(getColumnInfo),
|
||||
primaryKey: DatabaseAnalayser.extractPrimaryKeys(table, pkColumns.rows),
|
||||
foreignKeys: DatabaseAnalayser.extractForeignKeys(table, fkColumns.rows),
|
||||
})),
|
||||
views: views.rows.map((view) => ({
|
||||
...view,
|
||||
columns: columns.rows.filter((col) => col.pureName == view.pureName).map(getColumnInfo),
|
||||
createSql: viewTexts[view.pureName],
|
||||
requiresFormat: true,
|
||||
})),
|
||||
procedures: programmables.rows.filter((x) => x.objectType == 'PROCEDURE').map(fp.omit(['objectType'])),
|
||||
functions: programmables.rows.filter((x) => x.objectType == 'FUNCTION').map(fp.omit(['objectType'])),
|
||||
},
|
||||
(x) => x.pureName
|
||||
);
|
||||
}
|
||||
|
||||
getDeletedObjectsForField(nameArray, objectTypeField) {
|
||||
return this.structure[objectTypeField]
|
||||
.filter((x) => !nameArray.includes(x.pureName))
|
||||
.map((x) => ({
|
||||
oldName: _.pick(x, ['pureName']),
|
||||
action: 'remove',
|
||||
objectTypeField,
|
||||
}));
|
||||
}
|
||||
|
||||
getDeletedObjects(nameArray) {
|
||||
return [
|
||||
...this.getDeletedObjectsForField(nameArray, 'tables'),
|
||||
...this.getDeletedObjectsForField(nameArray, 'views'),
|
||||
...this.getDeletedObjectsForField(nameArray, 'procedures'),
|
||||
...this.getDeletedObjectsForField(nameArray, 'functions'),
|
||||
...this.getDeletedObjectsForField(nameArray, 'triggers'),
|
||||
];
|
||||
}
|
||||
|
||||
async getModifications() {
|
||||
const tableModificationsQueryData = await this.driver.query(this.pool, this.createQuery('tableModifications'));
|
||||
const procedureModificationsQueryData = await this.driver.query(
|
||||
this.pool,
|
||||
this.createQuery('procedureModifications')
|
||||
);
|
||||
const functionModificationsQueryData = await this.driver.query(
|
||||
this.pool,
|
||||
this.createQuery('functionModifications')
|
||||
);
|
||||
|
||||
const allModifications = _.compact([
|
||||
...tableModificationsQueryData.rows.map((x) => {
|
||||
if (x.objectType == 'BASE TABLE') return { ...x, objectTypeField: 'tables' };
|
||||
if (x.objectType == 'VIEW') return { ...x, objectTypeField: 'views' };
|
||||
return null;
|
||||
}),
|
||||
...procedureModificationsQueryData.rows.map((x) => ({
|
||||
objectTypeField: 'procedures',
|
||||
modifyDate: x.Modified,
|
||||
pureName: x.Name,
|
||||
})),
|
||||
...functionModificationsQueryData.rows.map((x) => ({
|
||||
objectTypeField: 'functions',
|
||||
modifyDate: x.Modified,
|
||||
pureName: x.Name,
|
||||
})),
|
||||
]);
|
||||
|
||||
// console.log('allModifications', allModifications);
|
||||
// console.log(
|
||||
// 'DATES',
|
||||
// this.structure.procedures.map((x) => x.modifyDate)
|
||||
// );
|
||||
// console.log('MOD - SRC', modifications);
|
||||
// console.log(
|
||||
// 'MODs',
|
||||
// this.structure.tables.map((x) => x.modifyDate)
|
||||
// );
|
||||
const modifications = allModifications.map((x) => {
|
||||
const { objectType, modifyDate, pureName } = x;
|
||||
const field = objectTypeToField(objectType);
|
||||
|
||||
if (!field || !this.structure[field]) return null;
|
||||
// @ts-ignore
|
||||
const obj = this.structure[field].find((x) => x.pureName == pureName);
|
||||
|
||||
// object not modified
|
||||
if (obj && Math.abs(new Date(modifyDate).getTime() - new Date(obj.modifyDate).getTime()) < 1000) return null;
|
||||
|
||||
// console.log('MODIFICATION OF ', field, pureName, modifyDate, obj.modifyDate);
|
||||
|
||||
/** @type {import('dbgate-types').DatabaseModification} */
|
||||
const action = obj
|
||||
? {
|
||||
newName: { pureName },
|
||||
oldName: _.pick(obj, ['pureName']),
|
||||
action: 'change',
|
||||
objectTypeField: field,
|
||||
}
|
||||
: {
|
||||
newName: { pureName },
|
||||
action: 'add',
|
||||
objectTypeField: field,
|
||||
};
|
||||
return action;
|
||||
});
|
||||
|
||||
return [..._.compact(modifications), ...this.getDeletedObjects([...allModifications.map((x) => x.pureName)])];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MySqlAnalyser;
|
||||
@@ -1,30 +0,0 @@
|
||||
const SqlDumper = require('../default/SqlDumper');
|
||||
|
||||
class MySqlDumper extends SqlDumper {
|
||||
/** @param type {import('dbgate-types').TransformType} */
|
||||
transform(type, dumpExpr) {
|
||||
switch (type) {
|
||||
case 'GROUP:YEAR':
|
||||
case 'YEAR':
|
||||
this.put('^year(%c)', dumpExpr);
|
||||
break;
|
||||
case 'MONTH':
|
||||
this.put('^month(%c)', dumpExpr);
|
||||
break;
|
||||
case 'DAY':
|
||||
this.put('^day(%c)', dumpExpr);
|
||||
break;
|
||||
case 'GROUP:MONTH':
|
||||
this.put("^date_format(%c, '%s')", dumpExpr, '%Y-%m');
|
||||
break;
|
||||
case 'GROUP:DAY':
|
||||
this.put("^date_format(%c, '%s')", dumpExpr, '%Y-%m-%d');
|
||||
break;
|
||||
default:
|
||||
dumpExpr();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MySqlDumper;
|
||||
@@ -1,153 +0,0 @@
|
||||
const driverBase = require('../default/driverBase');
|
||||
const MySqlAnalyser = require('./MySqlAnalyser');
|
||||
const MySqlDumper = require('./MySqlDumper');
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
const dialect = {
|
||||
rangeSelect: true,
|
||||
stringEscapeChar: '\\',
|
||||
fallbackDataType: 'longtext',
|
||||
quoteIdentifier(s) {
|
||||
return '`' + s + '`';
|
||||
},
|
||||
};
|
||||
|
||||
function extractColumns(fields) {
|
||||
if (fields)
|
||||
return fields.map((col) => ({
|
||||
columnName: col.name,
|
||||
}));
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
analyserClass: MySqlAnalyser,
|
||||
dumperClass: MySqlDumper,
|
||||
|
||||
async connect(nativeModules, { server, port, user, password, database }) {
|
||||
const connection = nativeModules.mysql.createConnection({
|
||||
host: server,
|
||||
port,
|
||||
user,
|
||||
password,
|
||||
database,
|
||||
});
|
||||
connection._database_name = database;
|
||||
connection._nativeModules = nativeModules;
|
||||
return connection;
|
||||
},
|
||||
async query(connection, sql) {
|
||||
if (sql == null) {
|
||||
return {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
connection.query(sql, function (error, results, fields) {
|
||||
if (error) reject(error);
|
||||
resolve({ rows: results, columns: extractColumns(fields) });
|
||||
});
|
||||
});
|
||||
},
|
||||
async stream(connection, sql, options) {
|
||||
const query = connection.query(sql);
|
||||
|
||||
// const handleInfo = (info) => {
|
||||
// const { message, lineNumber, procName } = info;
|
||||
// options.info({
|
||||
// message,
|
||||
// line: lineNumber,
|
||||
// procedure: procName,
|
||||
// time: new Date(),
|
||||
// severity: 'info',
|
||||
// });
|
||||
// };
|
||||
|
||||
const handleEnd = (result) => {
|
||||
// console.log('RESULT', result);
|
||||
options.done(result);
|
||||
};
|
||||
|
||||
const handleRow = (row) => {
|
||||
options.row(row);
|
||||
};
|
||||
|
||||
const handleFields = (columns) => {
|
||||
console.log('FIELDS', columns[0].name);
|
||||
options.recordset(extractColumns(columns));
|
||||
};
|
||||
|
||||
const handleError = (error) => {
|
||||
console.log('ERROR', error);
|
||||
const { message, lineNumber, procName } = error;
|
||||
options.info({
|
||||
message,
|
||||
line: lineNumber,
|
||||
procedure: procName,
|
||||
time: new Date(),
|
||||
severity: 'error',
|
||||
});
|
||||
};
|
||||
|
||||
query.on('error', handleError).on('fields', handleFields).on('result', handleRow).on('end', handleEnd);
|
||||
|
||||
return query;
|
||||
},
|
||||
async readQuery(connection, sql, structure) {
|
||||
const query = connection.query(sql);
|
||||
const { stream } = connection._nativeModules;
|
||||
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
});
|
||||
|
||||
query
|
||||
.on('error', (err) => {
|
||||
console.error(err);
|
||||
pass.end();
|
||||
})
|
||||
.on('fields', (fields) => pass.write(structure || { columns: extractColumns(fields) }))
|
||||
.on('result', (row) => pass.write(row))
|
||||
.on('end', () => pass.end());
|
||||
|
||||
return pass;
|
||||
},
|
||||
async getVersion(connection) {
|
||||
const { rows } = await this.query(connection, "show variables like 'version'");
|
||||
const version = rows[0].Value;
|
||||
return { version };
|
||||
},
|
||||
// async analyseFull(pool) {
|
||||
// const analyser = new MySqlAnalyser(pool, this);
|
||||
// return analyser.fullAnalysis();
|
||||
// },
|
||||
// async analyseIncremental(pool, structure) {
|
||||
// const analyser = new MySqlAnalyser(pool, this);
|
||||
// return analyser.incrementalAnalysis(structure);
|
||||
// },
|
||||
// async analyseSingleObject(pool, name, typeField = 'tables') {
|
||||
// const analyser = new MySqlAnalyser(pool, this);
|
||||
// analyser.singleObjectFilter = { ...name, typeField };
|
||||
// const res = await analyser.fullAnalysis();
|
||||
// return res.tables[0];
|
||||
// },
|
||||
// // @ts-ignore
|
||||
// analyseSingleTable(pool, name) {
|
||||
// return this.analyseSingleObject(pool, name, 'tables');
|
||||
// },
|
||||
async listDatabases(connection) {
|
||||
const { rows } = await this.query(connection, 'show databases');
|
||||
return rows.map((x) => ({ name: x.Database }));
|
||||
},
|
||||
// createDumper() {
|
||||
// return new MySqlDumper(this);
|
||||
// },
|
||||
dialect,
|
||||
engine: 'mysql',
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
@@ -1,15 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
TABLE_NAME as pureName,
|
||||
COLUMN_NAME as columnName,
|
||||
IS_NULLABLE as isNullable,
|
||||
DATA_TYPE as dataType,
|
||||
CHARACTER_MAXIMUM_LENGTH as charMaxLength,
|
||||
NUMERIC_PRECISION as numericPrecision,
|
||||
NUMERIC_SCALE as numericScale,
|
||||
COLUMN_DEFAULT as defaultValue,
|
||||
EXTRA as extra
|
||||
from INFORMATION_SCHEMA.COLUMNS
|
||||
where TABLE_SCHEMA = '#DATABASE#' and TABLE_NAME =[OBJECT_NAME_CONDITION]
|
||||
order by ORDINAL_POSITION
|
||||
`;
|
||||
@@ -1,17 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
REFERENTIAL_CONSTRAINTS.CONSTRAINT_NAME as constraintName,
|
||||
REFERENTIAL_CONSTRAINTS.TABLE_NAME as pureName,
|
||||
REFERENTIAL_CONSTRAINTS.UPDATE_RULE as updateAction,
|
||||
REFERENTIAL_CONSTRAINTS.DELETE_RULE as deleteAction,
|
||||
REFERENTIAL_CONSTRAINTS.REFERENCED_TABLE_NAME as refTableName,
|
||||
KEY_COLUMN_USAGE.COLUMN_NAME as columnName,
|
||||
KEY_COLUMN_USAGE.REFERENCED_COLUMN_NAME as refColumnName
|
||||
from INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
|
||||
inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
||||
on REFERENTIAL_CONSTRAINTS.TABLE_NAME = KEY_COLUMN_USAGE.TABLE_NAME
|
||||
and REFERENTIAL_CONSTRAINTS.CONSTRAINT_NAME = KEY_COLUMN_USAGE.CONSTRAINT_NAME
|
||||
and REFERENTIAL_CONSTRAINTS.CONSTRAINT_SCHEMA = KEY_COLUMN_USAGE.CONSTRAINT_SCHEMA
|
||||
where REFERENTIAL_CONSTRAINTS.CONSTRAINT_SCHEMA = '#DATABASE#' and REFERENTIAL_CONSTRAINTS.TABLE_NAME =[OBJECT_NAME_CONDITION]
|
||||
order by KEY_COLUMN_USAGE.ORDINAL_POSITION
|
||||
`;
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = `
|
||||
SHOW FUNCTION STATUS WHERE Db = '#DATABASE#'
|
||||
`;
|
||||
@@ -1,21 +0,0 @@
|
||||
const columns = require('./columns');
|
||||
const tables = require('./tables');
|
||||
const primaryKeys = require('./primaryKeys');
|
||||
const foreignKeys = require('./foreignKeys');
|
||||
const tableModifications = require('./tableModifications');
|
||||
const views = require('./views');
|
||||
const programmables = require('./programmables');
|
||||
const procedureModifications = require('./procedureModifications');
|
||||
const functionModifications = require('./functionModifications');
|
||||
|
||||
module.exports = {
|
||||
columns,
|
||||
tables,
|
||||
primaryKeys,
|
||||
foreignKeys,
|
||||
tableModifications,
|
||||
views,
|
||||
programmables,
|
||||
procedureModifications,
|
||||
functionModifications,
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
module.exports = `select
|
||||
TABLE_CONSTRAINTS.CONSTRAINT_NAME as constraintName,
|
||||
TABLE_CONSTRAINTS.TABLE_NAME as pureName,
|
||||
KEY_COLUMN_USAGE.COLUMN_NAME as columnName
|
||||
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS
|
||||
inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
||||
on TABLE_CONSTRAINTS.TABLE_NAME = KEY_COLUMN_USAGE.TABLE_NAME
|
||||
and TABLE_CONSTRAINTS.CONSTRAINT_NAME = KEY_COLUMN_USAGE.CONSTRAINT_NAME
|
||||
and TABLE_CONSTRAINTS.CONSTRAINT_SCHEMA = KEY_COLUMN_USAGE.CONSTRAINT_SCHEMA
|
||||
where TABLE_CONSTRAINTS.CONSTRAINT_SCHEMA = '#DATABASE#' and TABLE_CONSTRAINTS.TABLE_NAME =[OBJECT_NAME_CONDITION] AND TABLE_CONSTRAINTS.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||
order by KEY_COLUMN_USAGE.ORDINAL_POSITION
|
||||
`;
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = `
|
||||
SHOW PROCEDURE STATUS WHERE Db = '#DATABASE#'
|
||||
`;
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
ROUTINE_NAME as pureName,
|
||||
ROUTINE_TYPE as objectType,
|
||||
COALESCE(LAST_ALTERED, CREATED) as modifyDate,
|
||||
ROUTINE_DEFINITION as createSql
|
||||
from information_schema.routines
|
||||
where ROUTINE_SCHEMA = '#DATABASE#' and ROUTINE_NAME =[OBJECT_NAME_CONDITION]
|
||||
`;
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
TABLE_NAME as pureName,
|
||||
TABLE_TYPE as objectType,
|
||||
case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as modifyDate
|
||||
from information_schema.tables
|
||||
where TABLE_SCHEMA = '#DATABASE#'
|
||||
`;
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
TABLE_NAME as pureName,
|
||||
case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as modifyDate
|
||||
from information_schema.tables
|
||||
where TABLE_SCHEMA = '#DATABASE#' and TABLE_TYPE='BASE TABLE' and TABLE_NAME =[OBJECT_NAME_CONDITION];
|
||||
`;
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
TABLE_NAME as pureName,
|
||||
coalesce(UPDATE_TIME, CREATE_TIME) as modifyDate
|
||||
from information_schema.tables
|
||||
where TABLE_SCHEMA = '#DATABASE#' and TABLE_NAME =[OBJECT_NAME_CONDITION] and TABLE_TYPE = 'VIEW';
|
||||
`;
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "dbgate-engines",
|
||||
"version": "1.0.3",
|
||||
"main": "index.js",
|
||||
"typings": "./index.d.ts",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbshell/dbgate.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "GPL",
|
||||
"keywords": [
|
||||
"sql",
|
||||
"mssql",
|
||||
"mssql",
|
||||
"postgresql",
|
||||
"dbgate"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.149",
|
||||
"dbgate-types": "^1.0.0",
|
||||
"nodemon": "^2.0.2",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"dbgate-tools": "^1.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.0"
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
const fp = require('lodash/fp');
|
||||
const _ = require('lodash');
|
||||
const sql = require('./sql');
|
||||
|
||||
const DatabaseAnalayser = require('../default/DatabaseAnalyser');
|
||||
const { isTypeString, isTypeNumeric } = require('dbgate-tools');
|
||||
|
||||
function normalizeTypeName(dataType) {
|
||||
if (dataType == 'character varying') return 'varchar';
|
||||
if (dataType == 'timestamp without time zone') return 'timestamp';
|
||||
return dataType;
|
||||
}
|
||||
|
||||
function getColumnInfo({
|
||||
isNullable,
|
||||
isIdentity,
|
||||
columnName,
|
||||
dataType,
|
||||
charMaxLength,
|
||||
numericPrecision,
|
||||
numericScale,
|
||||
defaultValue,
|
||||
}) {
|
||||
const normDataType = normalizeTypeName(dataType);
|
||||
let fullDataType = normDataType;
|
||||
if (charMaxLength && isTypeString(normDataType)) fullDataType = `${normDataType}(${charMaxLength})`;
|
||||
if (numericPrecision && numericScale && isTypeNumeric(normDataType))
|
||||
fullDataType = `${normDataType}(${numericPrecision},${numericScale})`;
|
||||
return {
|
||||
columnName,
|
||||
dataType: fullDataType,
|
||||
notNull: !isNullable || isNullable == 'NO' || isNullable == 'no',
|
||||
autoIncrement: !!isIdentity,
|
||||
defaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
class PostgreAnalyser extends DatabaseAnalayser {
|
||||
constructor(pool, driver) {
|
||||
super(pool, driver);
|
||||
}
|
||||
|
||||
createQuery(resFileName, typeFields) {
|
||||
let res = sql[resFileName];
|
||||
|
||||
if (this.singleObjectFilter) {
|
||||
const { typeField, schemaName, pureName } = this.singleObjectFilter;
|
||||
if (!typeFields || !typeFields.includes(typeField)) return null;
|
||||
res = res.replace(/=OBJECT_ID_CONDITION/g, ` = '${typeField}:${schemaName || 'public'}.${pureName}'`);
|
||||
return res;
|
||||
}
|
||||
if (!this.modifications || !typeFields || this.modifications.length == 0) {
|
||||
res = res.replace(/=OBJECT_ID_CONDITION/g, ' is not null');
|
||||
} else {
|
||||
const filterNames = this.modifications
|
||||
.filter((x) => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change'))
|
||||
.filter((x) => x.newName)
|
||||
.map((x) => `${x.objectTypeField}:${x.newName.schemaName}.${x.newName.pureName}`);
|
||||
if (filterNames.length == 0) {
|
||||
res = res.replace(/=OBJECT_ID_CONDITION/g, ' IS NULL');
|
||||
} else {
|
||||
res = res.replace(/=OBJECT_ID_CONDITION/g, ` in (${filterNames.map((x) => `'${x}'`).join(',')})`);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
|
||||
// let res = sql[resFileName];
|
||||
// res = res.replace('=[OBJECT_ID_CONDITION]', ' is not null');
|
||||
// return res;
|
||||
}
|
||||
async _runAnalysis() {
|
||||
const tables = await this.driver.query(this.pool, this.createQuery('tableModifications', ['tables']));
|
||||
const columns = await this.driver.query(this.pool, this.createQuery('columns', ['tables']));
|
||||
const pkColumns = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables']));
|
||||
const fkColumns = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables']));
|
||||
const views = await this.driver.query(this.pool, this.createQuery('views', ['views']));
|
||||
const routines = await this.driver.query(this.pool, this.createQuery('routines', ['procedures', 'functions']));
|
||||
// console.log('PG fkColumns', fkColumns.rows);
|
||||
|
||||
return this.mergeAnalyseResult(
|
||||
{
|
||||
tables: tables.rows.map((table) => ({
|
||||
...table,
|
||||
columns: columns.rows
|
||||
.filter((col) => col.pureName == table.pureName && col.schemaName == table.schemaName)
|
||||
.map(getColumnInfo),
|
||||
primaryKey: DatabaseAnalayser.extractPrimaryKeys(table, pkColumns.rows),
|
||||
foreignKeys: DatabaseAnalayser.extractForeignKeys(table, fkColumns.rows),
|
||||
})),
|
||||
views: views.rows.map((view) => ({
|
||||
...view,
|
||||
columns: columns.rows
|
||||
.filter((col) => col.pureName == view.pureName && col.schemaName == view.schemaName)
|
||||
.map(getColumnInfo),
|
||||
})),
|
||||
procedures: routines.rows.filter((x) => x.objectType == 'PROCEDURE'),
|
||||
functions: routines.rows.filter((x) => x.objectType == 'FUNCTION'),
|
||||
},
|
||||
(x) => `${x.objectTypeField}:${x.schemaName}.${x.pureName}`
|
||||
);
|
||||
}
|
||||
|
||||
async getModifications() {
|
||||
const tableModificationsQueryData = await this.driver.query(this.pool, this.createQuery('tableModifications'));
|
||||
const viewModificationsQueryData = await this.driver.query(this.pool, this.createQuery('viewModifications'));
|
||||
const routineModificationsQueryData = await this.driver.query(this.pool, this.createQuery('routineModifications'));
|
||||
|
||||
const allModifications = _.compact([
|
||||
...tableModificationsQueryData.rows.map((x) => ({ ...x, objectTypeField: 'tables' })),
|
||||
...viewModificationsQueryData.rows.map((x) => ({ ...x, objectTypeField: 'views' })),
|
||||
...routineModificationsQueryData.rows
|
||||
.filter((x) => x.objectType == 'PROCEDURE')
|
||||
.map((x) => ({ ...x, objectTypeField: 'procedures' })),
|
||||
...routineModificationsQueryData.rows
|
||||
.filter((x) => x.objectType == 'FUNCTION')
|
||||
.map((x) => ({ ...x, objectTypeField: 'functions' })),
|
||||
]);
|
||||
|
||||
const modifications = allModifications.map((x) => {
|
||||
const { objectTypeField, hashCode, pureName, schemaName } = x;
|
||||
|
||||
if (!objectTypeField || !this.structure[objectTypeField]) return null;
|
||||
const obj = this.structure[objectTypeField].find((x) => x.pureName == pureName && x.schemaName == schemaName);
|
||||
|
||||
// object not modified
|
||||
if (obj && obj.hashCode == hashCode) return null;
|
||||
|
||||
// console.log('MODIFICATION OF ', objectTypeField, schemaName, pureName);
|
||||
|
||||
/** @type {import('dbgate-types').DatabaseModification} */
|
||||
const action = obj
|
||||
? {
|
||||
newName: { schemaName, pureName },
|
||||
oldName: _.pick(obj, ['schemaName', 'pureName']),
|
||||
action: 'change',
|
||||
objectTypeField,
|
||||
}
|
||||
: {
|
||||
newName: { schemaName, pureName },
|
||||
action: 'add',
|
||||
objectTypeField,
|
||||
};
|
||||
return action;
|
||||
});
|
||||
|
||||
return [
|
||||
..._.compact(modifications),
|
||||
...this.getDeletedObjects([...allModifications.map((x) => `${x.schemaName}.${x.pureName}`)]),
|
||||
];
|
||||
}
|
||||
|
||||
getDeletedObjectsForField(nameArray, objectTypeField) {
|
||||
return this.structure[objectTypeField]
|
||||
.filter((x) => !nameArray.includes(`${x.schemaName}.${x.pureName}`))
|
||||
.map((x) => ({
|
||||
oldName: _.pick(x, ['schemaName', 'pureName']),
|
||||
action: 'remove',
|
||||
objectTypeField,
|
||||
}));
|
||||
}
|
||||
|
||||
getDeletedObjects(nameArray) {
|
||||
return [
|
||||
...this.getDeletedObjectsForField(nameArray, 'tables'),
|
||||
...this.getDeletedObjectsForField(nameArray, 'views'),
|
||||
...this.getDeletedObjectsForField(nameArray, 'procedures'),
|
||||
...this.getDeletedObjectsForField(nameArray, 'functions'),
|
||||
...this.getDeletedObjectsForField(nameArray, 'triggers'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PostgreAnalyser;
|
||||
@@ -1,30 +0,0 @@
|
||||
const SqlDumper = require('../default/SqlDumper');
|
||||
|
||||
class PostgreDumper extends SqlDumper {
|
||||
/** @param type {import('dbgate-types').TransformType} */
|
||||
transform(type, dumpExpr) {
|
||||
switch (type) {
|
||||
case 'GROUP:YEAR':
|
||||
case 'YEAR':
|
||||
this.put('^extract(^year ^from %c)', dumpExpr);
|
||||
break;
|
||||
case 'MONTH':
|
||||
this.put('^extract(^month ^from %c)', dumpExpr);
|
||||
break;
|
||||
case 'DAY':
|
||||
this.put('^extract(^day ^from %c)', dumpExpr);
|
||||
break;
|
||||
case 'GROUP:MONTH':
|
||||
this.put("^to_char(%c, '%s')", dumpExpr, 'YYYY-MM');
|
||||
break;
|
||||
case 'GROUP:DAY':
|
||||
this.put("^to_char(%c, '%s')", dumpExpr, 'YYYY-MM-DD');
|
||||
break;
|
||||
default:
|
||||
dumpExpr();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PostgreDumper;
|
||||
@@ -1,185 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
const driverBase = require('../default/driverBase');
|
||||
const PostgreAnalyser = require('./PostgreAnalyser');
|
||||
const PostgreDumper = require('./PostgreDumper');
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
const dialect = {
|
||||
rangeSelect: true,
|
||||
// stringEscapeChar: '\\',
|
||||
stringEscapeChar: "'",
|
||||
fallbackDataType: 'varchar',
|
||||
quoteIdentifier(s) {
|
||||
return '"' + s + '"';
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
analyserClass: PostgreAnalyser,
|
||||
dumperClass: PostgreDumper,
|
||||
|
||||
async connect(nativeModules, { server, port, user, password, database }) {
|
||||
const client = new nativeModules.pg.Client({
|
||||
host: server,
|
||||
port,
|
||||
user,
|
||||
password,
|
||||
database: database || 'postgres',
|
||||
});
|
||||
await client.connect();
|
||||
client._nativeModules = nativeModules;
|
||||
return client;
|
||||
},
|
||||
async query(client, sql) {
|
||||
if (sql == null) {
|
||||
return {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
}
|
||||
const res = await client.query(sql);
|
||||
return { rows: res.rows, columns: res.fields };
|
||||
},
|
||||
async stream(client, sql, options) {
|
||||
const query = new client._nativeModules.pgQueryStream(sql);
|
||||
const stream = client.query(query);
|
||||
|
||||
// const handleInfo = (info) => {
|
||||
// const { message, lineNumber, procName } = info;
|
||||
// options.info({
|
||||
// message,
|
||||
// line: lineNumber,
|
||||
// procedure: procName,
|
||||
// time: new Date(),
|
||||
// severity: 'info',
|
||||
// });
|
||||
// };
|
||||
|
||||
let wasHeader = false;
|
||||
|
||||
const handleEnd = (result) => {
|
||||
// console.log('RESULT', result);
|
||||
options.done(result);
|
||||
};
|
||||
|
||||
const handleReadable = () => {
|
||||
let row = stream.read();
|
||||
if (!wasHeader && row) {
|
||||
options.recordset(_.keys(row).map((columnName) => ({ columnName })));
|
||||
wasHeader = true;
|
||||
}
|
||||
|
||||
while (row) {
|
||||
options.row(row);
|
||||
row = stream.read();
|
||||
}
|
||||
};
|
||||
|
||||
// const handleFields = (columns) => {
|
||||
// // console.log('FIELDS', columns[0].name);
|
||||
// options.recordset(columns);
|
||||
// // options.recordset(extractColumns(columns));
|
||||
// };
|
||||
|
||||
const handleError = (error) => {
|
||||
console.log('ERROR', error);
|
||||
const { message, lineNumber, procName } = error;
|
||||
options.info({
|
||||
message,
|
||||
line: lineNumber,
|
||||
procedure: procName,
|
||||
time: new Date(),
|
||||
severity: 'error',
|
||||
});
|
||||
};
|
||||
|
||||
stream.on('error', handleError);
|
||||
stream.on('readable', handleReadable);
|
||||
// stream.on('result', handleRow)
|
||||
// stream.on('data', handleRow)
|
||||
stream.on('end', handleEnd);
|
||||
|
||||
return stream;
|
||||
},
|
||||
// async analyseSingleObject(pool, name, typeField = 'tables') {
|
||||
// const analyser = new PostgreAnalyser(pool, this);
|
||||
// analyser.singleObjectFilter = { ...name, typeField };
|
||||
// const res = await analyser.fullAnalysis();
|
||||
// return res.tables[0];
|
||||
// },
|
||||
// // @ts-ignore
|
||||
// analyseSingleTable(pool, name) {
|
||||
// return this.analyseSingleObject(pool, name, 'tables');
|
||||
// },
|
||||
async getVersion(client) {
|
||||
const { rows } = await this.query(client, 'SELECT version()');
|
||||
const { version } = rows[0];
|
||||
return { version };
|
||||
},
|
||||
// async analyseFull(pool) {
|
||||
// const analyser = new PostgreAnalyser(pool, this);
|
||||
// return analyser.fullAnalysis();
|
||||
// },
|
||||
// async analyseIncremental(pool, structure) {
|
||||
// const analyser = new PostgreAnalyser(pool, this);
|
||||
// return analyser.incrementalAnalysis(structure);
|
||||
// },
|
||||
async readQuery(client, sql, structure) {
|
||||
const query = new client._nativeModules.pgQueryStream(sql);
|
||||
const { stream } = client._nativeModules;
|
||||
|
||||
const queryStream = client.query(query);
|
||||
|
||||
let wasHeader = false;
|
||||
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
});
|
||||
|
||||
const handleEnd = (result) => {
|
||||
pass.end();
|
||||
};
|
||||
|
||||
const handleReadable = () => {
|
||||
let row = queryStream.read();
|
||||
if (!wasHeader && row) {
|
||||
pass.write(
|
||||
structure || {
|
||||
columns: _.keys(row).map((columnName) => ({ columnName })),
|
||||
}
|
||||
);
|
||||
wasHeader = true;
|
||||
}
|
||||
|
||||
while (row) {
|
||||
pass.write(row);
|
||||
row = queryStream.read();
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (error) => {
|
||||
console.error(error);
|
||||
pass.end();
|
||||
};
|
||||
|
||||
queryStream.on('error', handleError);
|
||||
queryStream.on('readable', handleReadable);
|
||||
queryStream.on('end', handleEnd);
|
||||
|
||||
return pass;
|
||||
},
|
||||
// createDumper() {
|
||||
// return new PostgreDumper(this);
|
||||
// },
|
||||
async listDatabases(client) {
|
||||
const { rows } = await this.query(client, 'SELECT datname AS name FROM pg_database WHERE datistemplate = false');
|
||||
return rows;
|
||||
},
|
||||
dialect,
|
||||
engine: 'postgres',
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
@@ -1,19 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
table_schema as "schemaName",
|
||||
table_name as "pureName",
|
||||
column_name as "columnName",
|
||||
is_nullable as "isNullable",
|
||||
data_type as "dataType",
|
||||
character_maximum_length as "charMaxLength",
|
||||
numeric_precision as "numericPrecision",
|
||||
numeric_scale as "numericScale",
|
||||
column_default as "defaultValue"
|
||||
from information_schema.columns
|
||||
where
|
||||
table_schema <> 'information_schema'
|
||||
and table_schema <> 'pg_catalog'
|
||||
and table_schema !~ '^pg_toast'
|
||||
and 'tables:' || table_schema || '.' || table_name =OBJECT_ID_CONDITION
|
||||
order by ordinal_position
|
||||
`;
|
||||
@@ -1,24 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
fk.constraint_name as "constraintName",
|
||||
fk.constraint_schema as "constraintSchema",
|
||||
base.table_name as "pureName",
|
||||
base.table_schema as "schemaName",
|
||||
fk.update_rule as "updateAction",
|
||||
fk.delete_rule as "deleteAction",
|
||||
ref.table_name as "refTableName",
|
||||
ref.table_schema as "refSchemaName",
|
||||
basecol.column_name as "columnName",
|
||||
refcol.column_name as "refColumnName"
|
||||
from information_schema.referential_constraints fk
|
||||
inner join information_schema.table_constraints base on fk.constraint_name = base.constraint_name and fk.constraint_schema = base.constraint_schema
|
||||
inner join information_schema.table_constraints ref on fk.unique_constraint_name = ref.constraint_name and fk.unique_constraint_schema = ref.constraint_schema
|
||||
inner join information_schema.key_column_usage basecol on base.table_name = basecol.table_name and base.constraint_name = basecol.constraint_name
|
||||
inner join information_schema.key_column_usage refcol on ref.table_name = refcol.table_name and ref.constraint_name = refcol.constraint_name and basecol.ordinal_position = refcol.ordinal_position
|
||||
where
|
||||
base.table_schema <> 'information_schema'
|
||||
and base.table_schema <> 'pg_catalog'
|
||||
and base.table_schema !~ '^pg_toast'
|
||||
and 'tables:' || base.table_schema || '.' || base.table_name =OBJECT_ID_CONDITION
|
||||
order by basecol.ordinal_position
|
||||
`;
|
||||
@@ -1,19 +0,0 @@
|
||||
const columns = require('./columns');
|
||||
const tableModifications = require('./tableModifications');
|
||||
const viewModifications = require('./viewModifications');
|
||||
const primaryKeys = require('./primaryKeys');
|
||||
const foreignKeys = require('./foreignKeys');
|
||||
const views = require('./views');
|
||||
const routines = require('./routines');
|
||||
const routineModifications = require('./routineModifications');
|
||||
|
||||
module.exports = {
|
||||
columns,
|
||||
tableModifications,
|
||||
viewModifications,
|
||||
primaryKeys,
|
||||
foreignKeys,
|
||||
views,
|
||||
routines,
|
||||
routineModifications,
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
table_constraints.constraint_schema as "constraintSchema",
|
||||
table_constraints.constraint_name as "constraintName",
|
||||
table_constraints.table_schema as "schemaName",
|
||||
table_constraints.table_name as "pureName",
|
||||
key_column_usage.column_name as "columnName"
|
||||
from information_schema.table_constraints
|
||||
inner join information_schema.key_column_usage on table_constraints.table_name = key_column_usage.table_name and table_constraints.constraint_name = key_column_usage.constraint_name
|
||||
where
|
||||
table_constraints.table_schema <> 'information_schema'
|
||||
and table_constraints.table_schema <> 'pg_catalog'
|
||||
and table_constraints.table_schema !~ '^pg_toast'
|
||||
and table_constraints.constraint_type = 'PRIMARY KEY'
|
||||
and 'tables:' || table_constraints.table_schema || '.' || table_constraints.table_name =OBJECT_ID_CONDITION
|
||||
order by key_column_usage.ordinal_position
|
||||
`;
|
||||
@@ -1,10 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
routine_name as "pureName",
|
||||
routine_schema as "schemaName",
|
||||
md5(routine_definition) as "hashCode",
|
||||
routine_type as "objectType"
|
||||
from
|
||||
information_schema.routines where routine_schema != 'information_schema' and routine_schema != 'pg_catalog'
|
||||
and routine_type in ('PROCEDURE', 'FUNCTION')
|
||||
`;
|
||||
@@ -1,15 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
routine_name as "pureName",
|
||||
routine_schema as "schemaName",
|
||||
routine_definition as "createSql",
|
||||
md5(routine_definition) as "hashCode",
|
||||
routine_type as "objectType"
|
||||
from
|
||||
information_schema.routines where routine_schema != 'information_schema' and routine_schema != 'pg_catalog'
|
||||
and (
|
||||
(routine_type = 'PROCEDURE' and ('procedures:' || routine_schema || '.' || routine_schema) =OBJECT_ID_CONDITION)
|
||||
or
|
||||
(routine_type = 'FUNCTION' and ('functions:' || routine_schema || '.' || routine_schema) =OBJECT_ID_CONDITION)
|
||||
)
|
||||
`;
|
||||
@@ -1,52 +0,0 @@
|
||||
module.exports = `
|
||||
with pkey as
|
||||
(
|
||||
select cc.conrelid, format(E'create constraint %I primary key(%s);\\n', cc.conname,
|
||||
string_agg(a.attname, ', '
|
||||
order by array_position(cc.conkey, a.attnum))) pkey
|
||||
from pg_catalog.pg_constraint cc
|
||||
join pg_catalog.pg_class c on c.oid = cc.conrelid
|
||||
join pg_catalog.pg_attribute a on a.attrelid = cc.conrelid
|
||||
and a.attnum = any(cc.conkey)
|
||||
where cc.contype = 'p'
|
||||
group by cc.conrelid, cc.conname
|
||||
)
|
||||
|
||||
|
||||
SELECT oid as "objectId", nspname as "schemaName", relname as "pureName",
|
||||
md5('CREATE TABLE ' || nspname || '.' || relname || E'\\n(\\n' ||
|
||||
array_to_string(
|
||||
array_agg(
|
||||
' ' || column_name || ' ' || type || ' '|| not_null
|
||||
)
|
||||
, E',\\n'
|
||||
) || E'\\n);\\n' || coalesce((select pkey from pkey where pkey.conrelid = oid),'NO_PK')) as "hashCode"
|
||||
from
|
||||
(
|
||||
SELECT
|
||||
c.relname, a.attname AS column_name, c.oid,
|
||||
n.nspname,
|
||||
pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
|
||||
case
|
||||
when a.attnotnull
|
||||
then 'NOT NULL'
|
||||
else 'NULL'
|
||||
END as not_null
|
||||
FROM pg_class c,
|
||||
pg_namespace n,
|
||||
pg_attribute a,
|
||||
pg_type t
|
||||
|
||||
WHERE c.relkind = 'r'
|
||||
AND a.attnum > 0
|
||||
AND a.attrelid = c.oid
|
||||
AND a.atttypid = t.oid
|
||||
AND n.oid = c.relnamespace
|
||||
AND n.nspname <> 'pg_catalog'
|
||||
AND n.nspname <> 'information_schema'
|
||||
AND n.nspname !~ '^pg_toast'
|
||||
ORDER BY a.attnum
|
||||
) as tabledefinition
|
||||
where ('tables:' || nspname || '.' || relname) =OBJECT_ID_CONDITION
|
||||
group by relname, nspname, oid
|
||||
`;
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
table_name as "pureName",
|
||||
table_schema as "schemaName",
|
||||
md5(view_definition) as "hashCode"
|
||||
from
|
||||
information_schema.views where table_schema != 'information_schema' and table_schema != 'pg_catalog'
|
||||
`;
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = `
|
||||
select
|
||||
table_name as "pureName",
|
||||
table_schema as "schemaName",
|
||||
view_definition as "createSql",
|
||||
md5(view_definition) as "hashCode"
|
||||
from
|
||||
information_schema.views
|
||||
where table_schema != 'information_schema' and table_schema != 'pg_catalog'
|
||||
and ('views:' || table_schema || '.' || table_name) =OBJECT_ID_CONDITION
|
||||
`;
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
},
|
||||
"include": [
|
||||
"."
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { parseFilter } from './parseFilter';
|
||||
import { FilterType } from './types';
|
||||
import engines from 'dbgate-engines';
|
||||
import { dumpSqlCondition, treeToSql } from 'dbgate-sqltree';
|
||||
|
||||
const ast = parseFilter(process.argv[2], process.argv[3] as FilterType);
|
||||
|
||||
console.log(JSON.stringify(ast, null, ' '));
|
||||
|
||||
console.log('***************** MS SQL ******************');
|
||||
console.log(treeToSql(engines('mssql'), ast, dumpSqlCondition));
|
||||
|
||||
console.log('***************** MySql *******************');
|
||||
console.log(treeToSql(engines('mysql'), ast, dumpSqlCondition));
|
||||
|
||||
console.log('***************** Postgre *****************');
|
||||
console.log(treeToSql(engines('postgres'), ast, dumpSqlCondition));
|
||||
@@ -1,23 +1,20 @@
|
||||
{
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.4",
|
||||
"name": "dbgate-tools",
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbshell/dbgate.git"
|
||||
},
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "GPL",
|
||||
|
||||
"keywords": [
|
||||
"sql",
|
||||
"dbgate"
|
||||
],
|
||||
|
||||
"scripts": {
|
||||
"prepare": "yarn build",
|
||||
"build": "tsc",
|
||||
@@ -28,14 +25,13 @@
|
||||
"lib"
|
||||
],
|
||||
"devDependencies": {
|
||||
"dbgate-types": "^1.0.0",
|
||||
"@types/node": "^13.7.0",
|
||||
"dbgate-types": "^1.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"ts-jest": "^25.2.1",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.0"
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
const _ = require('lodash');
|
||||
const fp = require('lodash/fp');
|
||||
import { DatabaseInfo, DatabaseModification, EngineDriver } from 'dbgate-types';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
import _pick from 'lodash/pick';
|
||||
|
||||
class DatabaseAnalyser {
|
||||
/**
|
||||
*
|
||||
* @param {import('dbgate-types').EngineDriver} driver
|
||||
*/
|
||||
constructor(pool, driver) {
|
||||
this.pool = pool;
|
||||
this.driver = driver;
|
||||
// this.result = DatabaseAnalyser.createEmptyStructure();
|
||||
/** @type {import('dbgate-types').DatabaseInfo} */
|
||||
this.structure = null;
|
||||
/** import('dbgate-types').DatabaseModification[]) */
|
||||
this.modifications = null;
|
||||
this.singleObjectFilter = null;
|
||||
}
|
||||
const fp_pick = (arg) => (array) => _pick(array, arg);
|
||||
export class DatabaseAnalyser {
|
||||
structure: DatabaseInfo;
|
||||
modifications: DatabaseModification[];
|
||||
singleObjectFilter: any;
|
||||
|
||||
constructor(public pool, public driver: EngineDriver) {}
|
||||
|
||||
async _runAnalysis() {
|
||||
return DatabaseAnalyser.createEmptyStructure();
|
||||
@@ -62,7 +56,7 @@ class DatabaseAnalyser {
|
||||
const newArray = newlyAnalysed[field] || [];
|
||||
const addedChangedIds = newArray.map((x) => extractObjectId(x));
|
||||
const removeAllIds = [...removedIds, ...addedChangedIds];
|
||||
res[field] = _.sortBy(
|
||||
res[field] = _sortBy(
|
||||
[...this.structure[field].filter((x) => !removeAllIds.includes(extractObjectId(x))), ...newArray],
|
||||
(x) => x.pureName
|
||||
);
|
||||
@@ -80,46 +74,46 @@ class DatabaseAnalyser {
|
||||
// findObjectById(id) {
|
||||
// return this.structure.tables.find((x) => x.objectId == id);
|
||||
// }
|
||||
|
||||
static createEmptyStructure(): DatabaseInfo {
|
||||
return {
|
||||
tables: [],
|
||||
views: [],
|
||||
functions: [],
|
||||
procedures: [],
|
||||
triggers: [],
|
||||
schemas: [],
|
||||
};
|
||||
}
|
||||
|
||||
static byTableFilter(table) {
|
||||
return (x) => x.pureName == table.pureName && x.schemaName == x.schemaName;
|
||||
}
|
||||
|
||||
static extractPrimaryKeys(table, pkColumns) {
|
||||
const filtered = pkColumns.filter(DatabaseAnalyser.byTableFilter(table));
|
||||
if (filtered.length == 0) return undefined;
|
||||
return {
|
||||
..._pick(filtered[0], ['constraintName', 'schemaName', 'pureName']),
|
||||
constraintType: 'primaryKey',
|
||||
columns: filtered.map(fp_pick('columnName')),
|
||||
};
|
||||
}
|
||||
static extractForeignKeys(table, fkColumns) {
|
||||
const grouped = _groupBy(fkColumns.filter(DatabaseAnalyser.byTableFilter(table)), 'constraintName');
|
||||
return Object.keys(grouped).map((constraintName) => ({
|
||||
constraintName,
|
||||
constraintType: 'foreignKey',
|
||||
..._pick(grouped[constraintName][0], [
|
||||
'constraintName',
|
||||
'schemaName',
|
||||
'pureName',
|
||||
'refSchemaName',
|
||||
'refTableName',
|
||||
'updateAction',
|
||||
'deleteAction',
|
||||
]),
|
||||
columns: grouped[constraintName].map(fp_pick(['columnName', 'refColumnName'])),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {import('dbgate-types').DatabaseInfo} */
|
||||
DatabaseAnalyser.createEmptyStructure = () => ({
|
||||
tables: [],
|
||||
views: [],
|
||||
functions: [],
|
||||
procedures: [],
|
||||
triggers: [],
|
||||
schemas: [],
|
||||
});
|
||||
|
||||
DatabaseAnalyser.byTableFilter = (table) => (x) => x.pureName == table.pureName && x.schemaName == x.schemaName;
|
||||
|
||||
DatabaseAnalyser.extractPrimaryKeys = (table, pkColumns) => {
|
||||
const filtered = pkColumns.filter(DatabaseAnalyser.byTableFilter(table));
|
||||
if (filtered.length == 0) return undefined;
|
||||
return {
|
||||
..._.pick(filtered[0], ['constraintName', 'schemaName', 'pureName']),
|
||||
constraintType: 'primaryKey',
|
||||
columns: filtered.map(fp.pick('columnName')),
|
||||
};
|
||||
};
|
||||
|
||||
DatabaseAnalyser.extractForeignKeys = (table, fkColumns) => {
|
||||
const grouped = _.groupBy(fkColumns.filter(DatabaseAnalyser.byTableFilter(table)), 'constraintName');
|
||||
return _.keys(grouped).map((constraintName) => ({
|
||||
constraintName,
|
||||
constraintType: 'foreignKey',
|
||||
..._.pick(grouped[constraintName][0], [
|
||||
'constraintName',
|
||||
'schemaName',
|
||||
'pureName',
|
||||
'refSchemaName',
|
||||
'refTableName',
|
||||
'updateAction',
|
||||
'deleteAction',
|
||||
]),
|
||||
columns: grouped[constraintName].map(fp.pick(['columnName', 'refColumnName'])),
|
||||
}));
|
||||
};
|
||||
|
||||
module.exports = DatabaseAnalyser;
|
||||
@@ -1,13 +1,25 @@
|
||||
const _ = require('lodash');
|
||||
const moment = require('moment');
|
||||
import {
|
||||
ColumnInfo,
|
||||
EngineDriver,
|
||||
ForeignKeyInfo,
|
||||
NamedObjectInfo,
|
||||
SqlDialect,
|
||||
TableInfo,
|
||||
TransformType,
|
||||
} from 'dbgate-types';
|
||||
import _isString from 'lodash/isString'
|
||||
import _isNumber from 'lodash/isNumber'
|
||||
import _isDate from 'lodash/isDate'
|
||||
|
||||
class SqlDumper {
|
||||
/** @param driver {import('dbgate-types').EngineDriver} */
|
||||
constructor(driver) {
|
||||
this.s = '';
|
||||
export class SqlDumper {
|
||||
s = '';
|
||||
driver: EngineDriver;
|
||||
dialect: SqlDialect;
|
||||
indentLevel = 0;
|
||||
|
||||
constructor(driver: EngineDriver) {
|
||||
this.driver = driver;
|
||||
this.dialect = driver.dialect;
|
||||
this.indentLevel = 0;
|
||||
}
|
||||
endCommand() {
|
||||
this.putRaw(';\n');
|
||||
@@ -36,9 +48,9 @@ class SqlDumper {
|
||||
if (value === null) this.putRaw('NULL');
|
||||
if (value === true) this.putRaw('1');
|
||||
if (value === false) this.putRaw('0');
|
||||
else if (_.isString(value)) this.putStringValue(value);
|
||||
else if (_.isNumber(value)) this.putRaw(value.toString());
|
||||
else if (_.isDate(value)) this.putStringValue(moment(value).toISOString());
|
||||
else if (_isString(value)) this.putStringValue(value);
|
||||
else if (_isNumber(value)) this.putRaw(value.toString());
|
||||
else if (_isDate(value)) this.putStringValue(new Date(value).toISOString());
|
||||
}
|
||||
putCmd(format, ...args) {
|
||||
this.put(format, ...args);
|
||||
@@ -85,8 +97,7 @@ class SqlDumper {
|
||||
if (!collection) return;
|
||||
this.putCollection(', ', collection, (item) => this.putFormattedValue(c, item));
|
||||
}
|
||||
/** @param format {string} */
|
||||
put(format, ...args) {
|
||||
put(format: string, ...args) {
|
||||
let i = 0;
|
||||
let argIndex = 0;
|
||||
const length = format.length;
|
||||
@@ -149,10 +160,7 @@ class SqlDumper {
|
||||
this.put(' ^auto_increment');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param column {import('dbgate-types').ColumnInfo}
|
||||
*/
|
||||
columnDefinition(column, { includeDefault = true, includeNullable = true, includeCollate = true } = {}) {
|
||||
columnDefinition(column: ColumnInfo, { includeDefault = true, includeNullable = true, includeCollate = true } = {}) {
|
||||
if (column.computedExpression) {
|
||||
this.put('^as %s', column.computedExpression);
|
||||
if (column.isPersisted) this.put(' ^persisted');
|
||||
@@ -175,10 +183,7 @@ class SqlDumper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param column {import('dbgate-types').ColumnInfo}
|
||||
*/
|
||||
columnDefault(column) {
|
||||
columnDefault(column: ColumnInfo) {
|
||||
if (column.defaultConstraint != null) {
|
||||
this.put(' ^constraint %i ^default %s ', column.defaultConstraint, column.defaultValue);
|
||||
} else {
|
||||
@@ -186,13 +191,7 @@ class SqlDumper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {string} delimiter
|
||||
* @param {T[]} collection
|
||||
* @param {(col: T) => void} lambda
|
||||
*/
|
||||
putCollection(delimiter, collection, lambda) {
|
||||
putCollection<T>(delimiter: string, collection: T[], lambda: (col: T) => void) {
|
||||
if (!collection) return;
|
||||
let first = true;
|
||||
for (const item of collection) {
|
||||
@@ -202,8 +201,7 @@ class SqlDumper {
|
||||
}
|
||||
}
|
||||
|
||||
/** @param table {import('dbgate-types').TableInfo} */
|
||||
createTable(table) {
|
||||
createTable(table: TableInfo) {
|
||||
this.put('^create ^table %f ( &>&n', table);
|
||||
this.putCollection(',&n', table.columns, (col) => {
|
||||
this.put('%i ', col.columnName);
|
||||
@@ -245,8 +243,7 @@ class SqlDumper {
|
||||
// }
|
||||
}
|
||||
|
||||
/** @param fk {import('dbgate-types').ForeignKeyInfo} */
|
||||
createForeignKeyFore(fk) {
|
||||
createForeignKeyFore(fk: ForeignKeyInfo) {
|
||||
if (fk.constraintName != null) this.put('^constraint %i ', fk.constraintName);
|
||||
this.put(
|
||||
'^foreign ^key (%,i) ^references %f (%,i)',
|
||||
@@ -258,16 +255,9 @@ class SqlDumper {
|
||||
if (fk.updateAction) this.put(' ^on ^update %k', fk.updateAction);
|
||||
}
|
||||
|
||||
/** @param type {import('dbgate-types').TransformType} */
|
||||
transform(type, dumpExpr) {
|
||||
transform(type: TransformType, dumpExpr) {
|
||||
dumpExpr();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param table {import('dbgate-types').NamedObjectInfo}
|
||||
* @param allow {boolean}
|
||||
*/
|
||||
allowIdentityInsert(table, allow) {}
|
||||
allowIdentityInsert(table: NamedObjectInfo, allow: boolean) {}
|
||||
}
|
||||
|
||||
module.exports = SqlDumper;
|
||||
@@ -1,11 +1,8 @@
|
||||
const { prepareTableForImport } = require('dbgate-tools');
|
||||
const _ = require('lodash');
|
||||
import { EngineDriver } from 'dbgate-types';
|
||||
import _intersection from 'lodash/intersection';
|
||||
import { prepareTableForImport } from './tableTransforms';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('dbgate-types').EngineDriver} driver
|
||||
*/
|
||||
function createBulkInsertStreamBase(driver, stream, pool, name, options) {
|
||||
export function createBulkInsertStreamBase(driver, stream, pool, name, options): any {
|
||||
const fullNameQuoted = name.schemaName
|
||||
? `${driver.dialect.quoteIdentifier(name.schemaName)}.${driver.dialect.quoteIdentifier(name.pureName)}`
|
||||
: driver.dialect.quoteIdentifier(name.pureName);
|
||||
@@ -46,7 +43,7 @@ function createBulkInsertStreamBase(driver, stream, pool, name, options) {
|
||||
await driver.query(pool, `TRUNCATE TABLE ${fullNameQuoted}`);
|
||||
}
|
||||
|
||||
this.columnNames = _.intersection(
|
||||
this.columnNames = _intersection(
|
||||
structure.columns.map((x) => x.columnName),
|
||||
writable.structure.columns.map((x) => x.columnName)
|
||||
);
|
||||
@@ -94,5 +91,3 @@ function createBulkInsertStreamBase(driver, stream, pool, name, options) {
|
||||
|
||||
return writable;
|
||||
}
|
||||
|
||||
module.exports = createBulkInsertStreamBase;
|
||||
@@ -1,8 +1,20 @@
|
||||
const createBulkInsertStreamBase = require('./createBulkInsertStreamBase');
|
||||
import { SqlDumper } from "./SqlDumper";
|
||||
|
||||
const driverBase = {
|
||||
const dialect = {
|
||||
limitSelect: true,
|
||||
rangeSelect: true,
|
||||
offsetFetchRangeSyntax: true,
|
||||
stringEscapeChar: "'",
|
||||
fallbackDataType: 'nvarchar(max)',
|
||||
quoteIdentifier(s) {
|
||||
return s;
|
||||
},
|
||||
};
|
||||
|
||||
export const driverBase = {
|
||||
analyserClass: null,
|
||||
dumperClass: null,
|
||||
dumperClass: SqlDumper,
|
||||
dialect,
|
||||
|
||||
async analyseFull(pool) {
|
||||
const analyser = new this.analyserClass(pool, this);
|
||||
@@ -24,11 +36,4 @@ const driverBase = {
|
||||
createDumper() {
|
||||
return new this.dumperClass(this);
|
||||
},
|
||||
async writeTable(pool, name, options) {
|
||||
const { stream, mssql } = pool._nativeModules;
|
||||
// @ts-ignore
|
||||
return createBulkInsertStreamBase(this, stream, pool, name, options);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = driverBase;
|
||||
@@ -1,3 +1,8 @@
|
||||
export * from './commonTypeParser';
|
||||
export * from './nameTools';
|
||||
export * from './tableTransforms';
|
||||
export * from './packageTools';
|
||||
export * from './createBulkInsertStreamBase';
|
||||
export * from './DatabaseAnalyser';
|
||||
export * from './driverBase';
|
||||
export * from './SqlDumper';
|
||||
|
||||
40
packages/tools/src/packageTools.ts
Normal file
40
packages/tools/src/packageTools.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { EngineDriver, ExtensionsDirectory } from 'dbgate-types';
|
||||
import _camelCase from 'lodash/camelCase';
|
||||
import _isString from 'lodash/isString';
|
||||
import _isPlainObject from 'lodash/isPlainObject';
|
||||
|
||||
export function extractShellApiPlugins(functionName, props): string[] {
|
||||
const res = [];
|
||||
const nsMatch = functionName.match(/^([^@]+)@([^@]+)/);
|
||||
if (nsMatch) {
|
||||
res.push(nsMatch[2]);
|
||||
}
|
||||
if (props && props.connection && props.connection.engine) {
|
||||
const nsMatchEngine = props.connection.engine.match(/^([^@]+)@([^@]+)/);
|
||||
if (nsMatchEngine) {
|
||||
res.push(nsMatchEngine[2]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function extractShellApiFunctionName(functionName) {
|
||||
const nsMatch = functionName.match(/^([^@]+)@([^@]+)/);
|
||||
if (nsMatch) {
|
||||
return `${_camelCase(nsMatch[2])}.shellApi.${nsMatch[1]}`;
|
||||
}
|
||||
return `dbgateApi.${functionName}`;
|
||||
}
|
||||
|
||||
export function findEngineDriver(connection, extensions: ExtensionsDirectory): EngineDriver {
|
||||
if (_isString(connection)) {
|
||||
return extensions.drivers.find((x) => x.engine == connection);
|
||||
}
|
||||
if (_isPlainObject(connection)) {
|
||||
const { engine } = connection;
|
||||
if (engine) {
|
||||
return extensions.drivers.find((x) => x.engine == engine);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TableInfo } from 'dbgate-types';
|
||||
import _ from 'lodash';
|
||||
import _cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
export function prepareTableForImport(table: TableInfo): TableInfo {
|
||||
const res = _.cloneDeep(table);
|
||||
const res = _cloneDeep(table);
|
||||
res.foreignKeys = [];
|
||||
if (res.primaryKey) res.primaryKey.constraintName = null;
|
||||
return res;
|
||||
|
||||
9
packages/types/engines.d.ts
vendored
9
packages/types/engines.d.ts
vendored
@@ -7,9 +7,9 @@ import { DatabaseInfo, NamedObjectInfo, TableInfo, ViewInfo, ProcedureInfo, Func
|
||||
export interface StreamOptions {
|
||||
recordset: (columns) => void;
|
||||
row: (row) => void;
|
||||
error: (error) => void;
|
||||
done: (result) => void;
|
||||
info: (info) => void;
|
||||
error?: (error) => void;
|
||||
done?: (result) => void;
|
||||
info?: (info) => void;
|
||||
}
|
||||
|
||||
export interface WriteTableOptions {
|
||||
@@ -20,7 +20,8 @@ export interface WriteTableOptions {
|
||||
|
||||
export interface EngineDriver {
|
||||
engine: string;
|
||||
connect(nativeModules, { server, port, user, password, database }): any;
|
||||
title: string;
|
||||
connect({ server, port, user, password, database }): any;
|
||||
query(pool: any, sql: string): Promise<QueryResult>;
|
||||
stream(pool: any, sql: string, options: StreamOptions);
|
||||
readQuery(pool: any, sql: string, structure?: TableInfo): Promise<stream.Readable>;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { EngineDriver } from "./engines";
|
||||
|
||||
export interface FileFormatDefinition {
|
||||
storageType: string;
|
||||
extension: string;
|
||||
@@ -5,7 +7,7 @@ export interface FileFormatDefinition {
|
||||
readerFunc?: string;
|
||||
writerFunc?: string;
|
||||
args?: any[];
|
||||
addFilesToSourceList: (
|
||||
addFilesToSourceList?: (
|
||||
file: {
|
||||
full: string;
|
||||
},
|
||||
@@ -17,3 +19,15 @@ export interface FileFormatDefinition {
|
||||
getDefaultOutputName?: (sourceName, values) => string;
|
||||
getOutputParams?: (sourceName, values) => any;
|
||||
}
|
||||
|
||||
export interface PluginDefinition {
|
||||
packageName: string;
|
||||
manifest: any;
|
||||
content: any;
|
||||
}
|
||||
|
||||
export interface ExtensionsDirectory {
|
||||
plugins: PluginDefinition[];
|
||||
fileFormats: FileFormatDefinition[];
|
||||
drivers: EngineDriver[];
|
||||
}
|
||||
1
packages/types/index.d.ts
vendored
1
packages/types/index.d.ts
vendored
@@ -39,3 +39,4 @@ export * from './query';
|
||||
export * from './dialect';
|
||||
export * from './dumper';
|
||||
export * from './dbtypes';
|
||||
export * from './extensions';
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^5.0.0",
|
||||
"dbgate-datalib": "^1.0.0",
|
||||
"dbgate-engines": "^1.0.0",
|
||||
"dbgate-sqltree": "^1.0.0",
|
||||
"@mdi/font": "^5.8.55",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
@@ -14,6 +11,9 @@
|
||||
"ace-builds": "^1.4.8",
|
||||
"axios": "^0.19.0",
|
||||
"cross-env": "^6.0.3",
|
||||
"dbgate-datalib": "^1.0.0",
|
||||
"dbgate-sqltree": "^1.0.0",
|
||||
"dbgate-tools": "^1.0.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"formik": "^2.1.0",
|
||||
@@ -24,6 +24,7 @@
|
||||
"react-dropzone": "^11.2.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-json-view": "^1.19.1",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-modal": "^3.11.1",
|
||||
"react-scripts": "3.3.0",
|
||||
"react-select": "^3.1.0",
|
||||
@@ -54,9 +55,9 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"dbgate-types": "^1.0.0",
|
||||
"@types/react": "^16.9.17",
|
||||
"@types/styled-components": "^4.4.2",
|
||||
"dbgate-types": "^1.0.0",
|
||||
"typescript": "^3.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
15
packages/web/public/unknown.svg
Normal file
15
packages/web/public/unknown.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="116" height="116" id="svg2">
|
||||
<defs id="defs4"/>
|
||||
<metadata id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<text x="10.710938" y="111.5" id="text2996" xml:space="preserve" style="font-size:144px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"><tspan x="10.710938" y="111.5" id="tspan2998" style="font-size:150px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial Bold">?</tspan></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -16,6 +16,8 @@ import ConnectionsPinger from './utility/ConnectionsPinger';
|
||||
import { ModalLayerProvider } from './modals/showModal';
|
||||
import UploadsProvider from './utility/UploadsProvider';
|
||||
import ThemeHelmet from './themes/ThemeHelmet';
|
||||
import PluginsProvider from './plugins/PluginsProvider';
|
||||
import { ExtensionsProvider } from './utility/useExtensions';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -27,16 +29,20 @@ function App() {
|
||||
<OpenedConnectionsProvider>
|
||||
<LeftPanelWidthProvider>
|
||||
<ConnectionsPinger>
|
||||
<ModalLayerProvider>
|
||||
<CurrentArchiveProvider>
|
||||
<CurrentThemeProvider>
|
||||
<UploadsProvider>
|
||||
<ThemeHelmet />
|
||||
<Screen />
|
||||
</UploadsProvider>
|
||||
</CurrentThemeProvider>
|
||||
</CurrentArchiveProvider>
|
||||
</ModalLayerProvider>
|
||||
<PluginsProvider>
|
||||
<ExtensionsProvider>
|
||||
<ModalLayerProvider>
|
||||
<CurrentArchiveProvider>
|
||||
<CurrentThemeProvider>
|
||||
<UploadsProvider>
|
||||
<ThemeHelmet />
|
||||
<Screen />
|
||||
</UploadsProvider>
|
||||
</CurrentThemeProvider>
|
||||
</CurrentArchiveProvider>
|
||||
</ModalLayerProvider>
|
||||
</ExtensionsProvider>
|
||||
</PluginsProvider>
|
||||
</ConnectionsPinger>
|
||||
</LeftPanelWidthProvider>
|
||||
</OpenedConnectionsProvider>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { fileformats } from './fileformats';
|
||||
import { FontIcon } from './icons';
|
||||
import useTheme from './theme/useTheme';
|
||||
import useExtensions from './utility/useExtensions';
|
||||
|
||||
const TargetStyled = styled.div`
|
||||
position: fixed;
|
||||
@@ -40,6 +40,7 @@ const TitleWrapper = styled.div`
|
||||
|
||||
export default function DragAndDropFileTarget({ isDragActive, inputProps }) {
|
||||
const theme = useTheme();
|
||||
const { fileFormats } = useExtensions();
|
||||
return (
|
||||
!!isDragActive && (
|
||||
<TargetStyled theme={theme}>
|
||||
@@ -50,7 +51,7 @@ export default function DragAndDropFileTarget({ isDragActive, inputProps }) {
|
||||
<TitleWrapper>Drop the files to upload to DbGate</TitleWrapper>
|
||||
<InfoWrapper>
|
||||
Supported file types:{' '}
|
||||
{fileformats
|
||||
{fileFormats
|
||||
.filter((x) => x.readerFunc)
|
||||
.map((x) => x.name)
|
||||
.join(', ')}
|
||||
|
||||
@@ -3,9 +3,9 @@ import _ from 'lodash';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import ImportExportModal from '../modals/ImportExportModal';
|
||||
import { defaultFileFormat } from '../fileformats';
|
||||
import { getDefaultFileFormat } from '../utility/fileformats';
|
||||
|
||||
function Menu({ data, setOpenedTabs, showModal }) {
|
||||
function Menu({ data, setOpenedTabs, showModal, extensions }) {
|
||||
const { connection, name } = data;
|
||||
const tooltip = `${connection.displayName || connection.server}\n${name}`;
|
||||
|
||||
@@ -27,7 +27,7 @@ function Menu({ data, setOpenedTabs, showModal }) {
|
||||
<ImportExportModal
|
||||
modalState={modalState}
|
||||
initialValues={{
|
||||
sourceStorageType: defaultFileFormat.storageType,
|
||||
sourceStorageType: getDefaultFileFormat(extensions).storageType,
|
||||
targetStorageType: 'database',
|
||||
targetConnectionId: data.connection._id,
|
||||
targetDatabaseName: data.name,
|
||||
@@ -41,7 +41,7 @@ function Menu({ data, setOpenedTabs, showModal }) {
|
||||
<ImportExportModal
|
||||
modalState={modalState}
|
||||
initialValues={{
|
||||
targetStorageType: defaultFileFormat.storageType,
|
||||
targetStorageType: getDefaultFileFormat(extensions).storageType,
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: data.connection._id,
|
||||
sourceDatabaseName: data.name,
|
||||
|
||||
@@ -4,13 +4,14 @@ import DataGrid from './DataGrid';
|
||||
import styled from 'styled-components';
|
||||
import { TableGridDisplay, createGridConfig, createGridCache } from 'dbgate-datalib';
|
||||
import { getFilterValueExpression } from 'dbgate-filterparser';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { useConnectionInfo, getTableInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import engines from 'dbgate-engines';
|
||||
import useSocket from '../utility/SocketProvider';
|
||||
import { VerticalSplitter } from '../widgets/Splitter';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
import ReferenceHeader from './ReferenceHeader';
|
||||
import SqlDataGridCore from './SqlDataGridCore';
|
||||
import useExtensions from '../utility/useExtensions';
|
||||
|
||||
const ReferenceContainer = styled.div`
|
||||
position: absolute;
|
||||
@@ -49,6 +50,7 @@ export default function TableDataGrid({
|
||||
const [childCache, setChildCache] = React.useState(createGridCache());
|
||||
const [refReloadToken, setRefReloadToken] = React.useState(0);
|
||||
const [myLoadedTime, setMyLoadedTime] = React.useState(0);
|
||||
const extensions = useExtensions();
|
||||
|
||||
const { childConfig } = config;
|
||||
const setChildConfig = (value, reference = undefined) => {
|
||||
@@ -75,7 +77,7 @@ export default function TableDataGrid({
|
||||
return connection
|
||||
? new TableGridDisplay(
|
||||
{ schemaName, pureName },
|
||||
engines(connection),
|
||||
findEngineDriver(connection, extensions),
|
||||
config,
|
||||
setConfig,
|
||||
cache || myCache,
|
||||
@@ -94,9 +96,10 @@ export default function TableDataGrid({
|
||||
|
||||
React.useEffect(() => {
|
||||
const newDisplay = createDisplay();
|
||||
if (!newDisplay) return;
|
||||
if (display && display.isLoadedCorrectly && !newDisplay.isLoadedCorrectly) return;
|
||||
setDisplay(newDisplay);
|
||||
}, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo]);
|
||||
}, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo, extensions]);
|
||||
|
||||
const handleDatabaseStructureChanged = React.useCallback(() => {
|
||||
(setCache || setMyCache)(createGridCache());
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import fileFormatBase from './fileFormatBase';
|
||||
import { FileFormatDefinition } from './types';
|
||||
|
||||
const csvFormat: FileFormatDefinition = {
|
||||
...fileFormatBase,
|
||||
storageType: 'csv',
|
||||
extension: 'csv',
|
||||
name: 'CSV',
|
||||
readerFunc: 'csvReader',
|
||||
writerFunc: 'csvWriter',
|
||||
args: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'delimiter',
|
||||
label: 'Delimiter',
|
||||
options: [
|
||||
{ name: 'Comma (,)', value: ',' },
|
||||
{ name: 'Semicolon (;)', value: ';' },
|
||||
{ name: 'Tab', value: '\t' },
|
||||
{ name: 'Pipe (|)', value: '|' },
|
||||
],
|
||||
apiName: 'delimiter',
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'quoted',
|
||||
label: 'Quoted',
|
||||
apiName: 'quoted',
|
||||
direction: 'target',
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'header',
|
||||
label: 'Has header row',
|
||||
apiName: 'header',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default csvFormat;
|
||||
@@ -1,52 +0,0 @@
|
||||
import axios from '../utility/axios';
|
||||
import fileFormatBase from './fileFormatBase';
|
||||
import { FileFormatDefinition } from './types';
|
||||
|
||||
const excelFormat: FileFormatDefinition = {
|
||||
...fileFormatBase,
|
||||
storageType: 'excel',
|
||||
extension: 'xlsx',
|
||||
name: 'MS Excel',
|
||||
readerFunc: 'excelSheetReader',
|
||||
writerFunc: 'excelSheetWriter',
|
||||
|
||||
addFilesToSourceList: async (file, newSources, newValues) => {
|
||||
const resp = await axios.get(`files/analyse-excel?filePath=${encodeURIComponent(file.full)}`);
|
||||
const sheetNames = resp.data;
|
||||
for (const sheetName of sheetNames) {
|
||||
newSources.push(sheetName);
|
||||
newValues[`sourceFile_${sheetName}`] = {
|
||||
fileName: file.full,
|
||||
sheetName,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
args: [
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'singleFile',
|
||||
label: 'Create single file',
|
||||
direction: 'target',
|
||||
},
|
||||
],
|
||||
|
||||
getDefaultOutputName: (sourceName, values) => {
|
||||
if (values.target_excel_singleFile) {
|
||||
return sourceName;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getOutputParams: (sourceName, values) => {
|
||||
if (values.target_excel_singleFile) {
|
||||
return {
|
||||
sheetName: values[`targetName_${sourceName}`] || sourceName,
|
||||
fileName:'data.xlsx'
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
export default excelFormat;
|
||||
@@ -1,11 +0,0 @@
|
||||
const fileFormatBase = {
|
||||
addFilesToSourceList: async (file, newSources, newValues) => {
|
||||
const sourceName = file.name;
|
||||
newSources.push(sourceName);
|
||||
newValues[`sourceFile_${sourceName}`] = {
|
||||
fileName: file.full,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default fileFormatBase;
|
||||
@@ -1,20 +0,0 @@
|
||||
import csv from './csv';
|
||||
import jsonl from './jsonl';
|
||||
import excel from './excel';
|
||||
import { FileFormatDefinition } from './types';
|
||||
|
||||
export const fileformats = [csv, jsonl, excel];
|
||||
|
||||
export function findFileFormat(storageType): FileFormatDefinition {
|
||||
return fileformats.find((x) => x.storageType == storageType);
|
||||
}
|
||||
|
||||
export function getFileFormatDirections(format: FileFormatDefinition) {
|
||||
if (!format) return [];
|
||||
const res = [];
|
||||
if (format.readerFunc) res.push('source');
|
||||
if (format.writerFunc) res.push('target');
|
||||
return res;
|
||||
}
|
||||
|
||||
export const defaultFileFormat = csv;
|
||||
@@ -1,13 +0,0 @@
|
||||
import fileFormatBase from './fileFormatBase';
|
||||
import { FileFormatDefinition } from './types';
|
||||
|
||||
const jsonlFormat: FileFormatDefinition = {
|
||||
...fileFormatBase,
|
||||
storageType: 'jsonl',
|
||||
extension: 'jsonl',
|
||||
name: 'JSON lines',
|
||||
readerFunc: 'jsonLinesReader',
|
||||
writerFunc: 'jsonLinesWriter',
|
||||
};
|
||||
|
||||
export default jsonlFormat;
|
||||
@@ -42,6 +42,7 @@ const iconNames = {
|
||||
|
||||
'icon run': 'mdi mdi-play',
|
||||
'icon chevron-down': 'mdi mdi-chevron-down',
|
||||
'icon plugin': 'mdi mdi-toy-brick',
|
||||
|
||||
'img green-ok': 'mdi mdi-check-circle color-green-8',
|
||||
'img alert': 'mdi mdi-alert-circle color-blue-6',
|
||||
|
||||
@@ -12,10 +12,10 @@ import {
|
||||
FormArchiveFolderSelect,
|
||||
FormArchiveFilesSelect,
|
||||
} from '../utility/forms';
|
||||
import { useArchiveFiles, useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import { useArchiveFiles, useConnectionInfo, useDatabaseInfo, useInstalledPlugins } from '../utility/metadataLoaders';
|
||||
import TableControl, { TableColumn } from '../utility/TableControl';
|
||||
import { TextField, SelectField, CheckboxField } from '../utility/inputs';
|
||||
import { createPreviewReader, getActionOptions, getTargetName, isFileStorage } from './createImpExpScript';
|
||||
import { createPreviewReader, getActionOptions, getTargetName } from './createImpExpScript';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import ErrorInfo from '../widgets/ErrorInfo';
|
||||
import getAsArray from '../utility/getAsArray';
|
||||
@@ -25,8 +25,9 @@ import SqlEditor from '../sqleditor/SqlEditor';
|
||||
import { useUploadsProvider } from '../utility/UploadsProvider';
|
||||
import { FontIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import { fileformats, findFileFormat, getFileFormatDirections } from '../fileformats';
|
||||
import { findFileFormat, getFileFormatDirections } from '../utility/fileformats';
|
||||
import FormArgumentList from '../utility/FormArgumentList';
|
||||
import useExtensions from '../utility/useExtensions';
|
||||
|
||||
const Container = styled.div`
|
||||
// max-height: 50vh;
|
||||
@@ -89,22 +90,30 @@ const Title = styled.div`
|
||||
margin: 10px 0px;
|
||||
`;
|
||||
|
||||
function getFileFilters(storageType) {
|
||||
function getFileFilters(extensions, storageType) {
|
||||
const res = [];
|
||||
const format = findFileFormat(storageType);
|
||||
const format = findFileFormat(extensions, storageType);
|
||||
if (format) res.push({ name: format.name, extensions: [format.extension] });
|
||||
res.push({ name: 'All Files', extensions: ['*'] });
|
||||
return res;
|
||||
}
|
||||
|
||||
async function addFilesToSourceList(files, values, setValues, preferedStorageType, setPreviewSource) {
|
||||
async function addFilesToSourceListDefault(file, newSources, newValues) {
|
||||
const sourceName = file.name;
|
||||
newSources.push(sourceName);
|
||||
newValues[`sourceFile_${sourceName}`] = {
|
||||
fileName: file.full,
|
||||
};
|
||||
}
|
||||
|
||||
async function addFilesToSourceList(extensions, files, values, setValues, preferedStorageType, setPreviewSource) {
|
||||
const newSources = [];
|
||||
const newValues = {};
|
||||
const storage = preferedStorageType || values.sourceStorageType;
|
||||
for (const file of getAsArray(files)) {
|
||||
const format = findFileFormat(storage);
|
||||
if (format && format.addFilesToSourceList) {
|
||||
await format.addFilesToSourceList(file, newSources, newValues);
|
||||
const format = findFileFormat(extensions, storage);
|
||||
if (format) {
|
||||
await (format.addFilesToSourceList || addFilesToSourceListDefault)(file, newSources, newValues);
|
||||
}
|
||||
}
|
||||
newValues['sourceList'] = [...(values.sourceList || []).filter((x) => !newSources.includes(x)), ...newSources];
|
||||
@@ -124,17 +133,19 @@ function ElectronFilesInput() {
|
||||
const { values, setValues } = useFormikContext();
|
||||
const electron = getElectron();
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const extensions = useExtensions();
|
||||
|
||||
const handleClick = async () => {
|
||||
const files = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), {
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: getFileFilters(values.sourceStorageType),
|
||||
filters: getFileFilters(extensions, values.sourceStorageType),
|
||||
});
|
||||
if (files) {
|
||||
const path = window.require('path');
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await addFilesToSourceList(
|
||||
extensions,
|
||||
files.map((full) => ({
|
||||
full,
|
||||
...path.parse(full),
|
||||
@@ -175,6 +186,7 @@ function SourceTargetConfig({
|
||||
tablesField = undefined,
|
||||
engine = undefined,
|
||||
}) {
|
||||
const extensions = useExtensions();
|
||||
const theme = useTheme();
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
const types =
|
||||
@@ -182,7 +194,7 @@ function SourceTargetConfig({
|
||||
? [{ value: 'jsldata', label: 'Query result data', directions: ['source'] }]
|
||||
: [
|
||||
{ value: 'database', label: 'Database', directions: ['source', 'target'] },
|
||||
...fileformats.map((format) => ({
|
||||
...extensions.fileFormats.map((format) => ({
|
||||
value: format.storageType,
|
||||
label: `${format.name} files(s)`,
|
||||
directions: getFileFormatDirections(format),
|
||||
@@ -193,7 +205,7 @@ function SourceTargetConfig({
|
||||
const storageType = values[storageTypeField];
|
||||
const dbinfo = useDatabaseInfo({ conid: values[connectionIdField], database: values[databaseNameField] });
|
||||
const archiveFiles = useArchiveFiles({ folder: values[archiveFolderField] });
|
||||
const format = findFileFormat(storageType);
|
||||
const format = findFileFormat(extensions, storageType);
|
||||
return (
|
||||
<Column>
|
||||
{direction == 'source' && (
|
||||
@@ -340,10 +352,13 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC
|
||||
const { setUploadListener } = useUploadsProvider();
|
||||
const theme = useTheme();
|
||||
const [previewSource, setPreviewSource] = React.useState(null);
|
||||
const extensions = useExtensions();
|
||||
|
||||
const handleUpload = React.useCallback(
|
||||
(file) => {
|
||||
console.log('UPLOAD', extensions);
|
||||
addFilesToSourceList(
|
||||
extensions,
|
||||
[
|
||||
{
|
||||
full: file.filePath,
|
||||
@@ -357,7 +372,7 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC
|
||||
);
|
||||
// setFieldValue('sourceList', [...(sourceList || []), file.originalName]);
|
||||
},
|
||||
[setFieldValue, sourceList, values]
|
||||
[extensions, setFieldValue, sourceList, values]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -373,11 +388,11 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC
|
||||
}
|
||||
}, []);
|
||||
|
||||
const supportsPreview = !!findFileFormat(values.sourceStorageType);
|
||||
const supportsPreview = !!findFileFormat(extensions, values.sourceStorageType);
|
||||
|
||||
const handleChangePreviewSource = async () => {
|
||||
if (previewSource && supportsPreview) {
|
||||
const reader = await createPreviewReader(values, previewSource);
|
||||
const reader = await createPreviewReader(extensions, values, previewSource);
|
||||
if (onChangePreview) onChangePreview(reader);
|
||||
} else {
|
||||
onChangePreview(null);
|
||||
@@ -436,8 +451,8 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC
|
||||
header="Action"
|
||||
formatter={(row) => (
|
||||
<SelectField
|
||||
options={getActionOptions(row, values, targetDbinfo)}
|
||||
value={values[`actionType_${row}`] || getActionOptions(row, values, targetDbinfo)[0].value}
|
||||
options={getActionOptions(extensions, row, values, targetDbinfo)}
|
||||
value={values[`actionType_${row}`] || getActionOptions(extensions, row, values, targetDbinfo)[0].value}
|
||||
onChange={(e) => setFieldValue(`actionType_${row}`, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
@@ -447,7 +462,7 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC
|
||||
header="Target"
|
||||
formatter={(row) => (
|
||||
<TextField
|
||||
value={getTargetName(row, values)}
|
||||
value={getTargetName(extensions, row, values)}
|
||||
onChange={(e) => setFieldValue(`targetName_${row}`, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user