mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-25 05:16:00 +00:00
added plugins
This commit is contained in:
21
plugins/dbgate-plugin-csv/LICENSE
Normal file
21
plugins/dbgate-plugin-csv/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Jan Prochazka
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
60
plugins/dbgate-plugin-csv/README.md
Normal file
60
plugins/dbgate-plugin-csv/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://paypal.me/JanProchazkaCz/30eur)
|
||||
[](https://www.npmjs.com/package/dbgate-plugin-csv)
|
||||
|
||||
# dbgate-plugin-csv
|
||||
|
||||
CSV import/export plugin for DbGate
|
||||
|
||||
## Usage without DbGate
|
||||
|
||||
Export from fake object reader into CSV file. Fake object file can be replaced with other reader/writer factory functions, as described in
|
||||
[dbgate-api package](https://www.npmjs.com/package/dbgate-api)
|
||||
|
||||
```javascript
|
||||
const dbgateApi = require('dbgate-api');
|
||||
const dbgatePluginCsv = require("dbgate-plugin-csv");
|
||||
|
||||
dbgateApi.registerPlugins(dbgatePluginCsv);
|
||||
|
||||
|
||||
async function run() {
|
||||
const reader = await dbgateApi.fakeObjectReader();
|
||||
const writer = await dbgatePluginCsv.shellApi.writer({ fileName: 'myfile1.csv', separator: ';' });
|
||||
await dbgateApi.copyStream(reader, writer);
|
||||
|
||||
console.log('Finished job script');
|
||||
}
|
||||
dbgateApi.runScript(run);
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Factory functions
|
||||
|
||||
### shellApi.reader
|
||||
Reads CSV file
|
||||
```js
|
||||
const dbgatePluginCsv = require("dbgate-plugin-csv");
|
||||
const reader = await dbgatePluginCsv.shellApi.reader({
|
||||
fileName: 'test.csv',
|
||||
encoding: 'utf-8',
|
||||
header: true,
|
||||
delimiter: ',',
|
||||
quoted: false,
|
||||
limitRows: null
|
||||
});
|
||||
```
|
||||
|
||||
### shellApi.writer
|
||||
Writes CSV file
|
||||
```js
|
||||
const dbgatePluginCsv = require("dbgate-plugin-csv");
|
||||
const writer = await dbgatePluginCsv.shellApi.writer({
|
||||
fileName: 'test.csv',
|
||||
encoding: 'utf-8',
|
||||
header: true,
|
||||
delimiter: ',',
|
||||
quoted: false
|
||||
});
|
||||
```
|
||||
35
plugins/dbgate-plugin-csv/icon.svg
Normal file
35
plugins/dbgate-plugin-csv/icon.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
|
||||
<polygon style="fill:#EFEEEE;" points="64,0 64,384 288,384 384,288 384,0 "/>
|
||||
<polygon style="fill:#ABABAB;" points="288,288 288,384 384,288 "/>
|
||||
<polygon style="fill:#DEDEDD;" points="192,384 288,384 288,288 "/>
|
||||
<path style="fill:#448E47;" d="M0,96v112h256V96L0,96L0,96z"/>
|
||||
<g>
|
||||
<path style="fill:#FFFFFF;" d="M64.32,130.112c-1.184-2.288-3.344-3.424-6.48-3.424c-1.728,0-3.152,0.464-4.272,1.408
|
||||
c-1.12,0.928-2,2.416-2.64,4.496s-1.088,4.8-1.344,8.176c-0.272,3.36-0.384,7.472-0.384,12.336c0,5.184,0.176,9.376,0.528,12.576
|
||||
c0.336,3.2,0.896,5.664,1.632,7.44s1.664,2.96,2.784,3.552c1.12,0.608,2.416,0.928,3.888,0.928c1.216,0,2.352-0.208,3.408-0.624
|
||||
s1.968-1.248,2.736-2.496c0.784-1.248,1.392-3.008,1.824-5.28c0.448-2.272,0.672-5.264,0.672-8.976H80.48
|
||||
c0,3.696-0.288,7.232-0.864,10.56s-1.664,6.24-3.216,8.736c-1.584,2.48-3.776,4.432-6.624,5.84
|
||||
c-2.848,1.408-6.544,2.128-11.088,2.128c-5.168,0-9.312-0.848-12.368-2.496c-3.072-1.664-5.424-4.064-7.056-7.2
|
||||
s-2.688-6.88-3.168-11.232c-0.464-4.336-0.72-9.152-0.72-14.384c0-5.184,0.256-9.968,0.72-14.352
|
||||
c0.48-4.368,1.552-8.144,3.168-11.28c1.648-3.12,3.984-5.584,7.056-7.344c3.056-1.744,7.2-2.64,12.368-2.64
|
||||
c4.944,0,8.816,0.8,11.664,2.4c2.848,1.6,4.976,3.632,6.368,6.096s2.304,5.12,2.64,7.968c0.352,2.848,0.528,5.52,0.528,8.016H66.08
|
||||
C66.08,136,65.488,132.368,64.32,130.112z"/>
|
||||
<path style="fill:#FFFFFF;" d="M109.072,167.008c0,1.6,0.144,3.056,0.384,4.352c0.272,1.312,0.736,2.416,1.44,3.312
|
||||
c0.704,0.912,1.664,1.616,2.848,2.128c1.168,0.496,2.672,0.768,4.448,0.768c2.128,0,4.016-0.688,5.712-2.064
|
||||
c1.68-1.376,2.544-3.52,2.544-6.384c0-1.536-0.224-2.864-0.624-3.984c-0.416-1.12-1.104-2.128-2.064-3.008
|
||||
c-0.976-0.912-2.24-1.712-3.792-2.448s-3.504-1.488-5.808-2.256c-3.056-1.024-5.712-2.16-7.968-3.376
|
||||
c-2.24-1.2-4.112-2.624-5.616-4.272c-1.504-1.632-2.608-3.52-3.312-5.664c-0.704-2.16-1.056-4.624-1.056-7.456
|
||||
c0-6.784,1.888-11.824,5.664-15.152c3.76-3.328,8.96-4.992,15.552-4.992c3.072,0,5.904,0.336,8.496,1.008s4.832,1.744,6.72,3.264
|
||||
c1.888,1.504,3.36,3.424,4.416,5.744c1.04,2.336,1.584,5.136,1.584,8.4v1.92h-13.232c0-3.264-0.576-5.776-1.712-7.552
|
||||
c-1.152-1.744-3.072-2.64-5.76-2.64c-1.536,0-2.816,0.24-3.84,0.672c-1.008,0.448-1.84,1.04-2.448,1.776s-1.04,1.616-1.264,2.576
|
||||
c-0.24,0.96-0.336,1.952-0.336,2.976c0,2.128,0.448,3.888,1.344,5.328c0.896,1.456,2.816,2.784,5.76,3.984l10.656,4.608
|
||||
c2.624,1.152,4.768,2.352,6.416,3.616c1.664,1.248,3.008,2.592,3.984,4.032c0.992,1.44,1.68,3.008,2.064,4.752
|
||||
c0.384,1.712,0.576,3.648,0.576,5.744c0,7.232-2.096,12.496-6.288,15.792c-4.192,3.296-10.032,4.96-17.52,4.96
|
||||
c-7.808,0-13.392-1.696-16.768-5.088c-3.36-3.392-5.024-8.256-5.024-14.592v-2.784h13.824L109.072,167.008L109.072,167.008z"/>
|
||||
<path style="fill:#FFFFFF;" d="M177.344,168.544h0.304l10.176-50.688h14.32L186.4,186.4h-17.76l-15.728-68.544h14.784
|
||||
L177.344,168.544z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
39
plugins/dbgate-plugin-csv/package.json
Normal file
39
plugins/dbgate-plugin-csv/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "dbgate-plugin-csv",
|
||||
"main": "dist/backend.js",
|
||||
"version": "1.0.9",
|
||||
"homepage": "https://github.com/dbgate/dbgate-plugin-csv",
|
||||
"description": "CSV import/export plugin for DbGate",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate-plugin-csv.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "GPL",
|
||||
"keywords": [
|
||||
"csv",
|
||||
"import",
|
||||
"export",
|
||||
"dbgate",
|
||||
"dbgateplugin"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build:frontend": "webpack --config webpack-frontend.config",
|
||||
"build:backend": "webpack --config webpack-backend.config.js",
|
||||
"build": "yarn build:frontend && yarn build:backend",
|
||||
"plugin": "yarn build && yarn pack && dbgate-plugin dbgate-plugin-csv",
|
||||
"plugout": "dbgate-plugout dbgate-plugin-csv",
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"csv": "^5.3.2",
|
||||
"dbgate-plugin-tools": "^1.0.4",
|
||||
"lodash": "^4.17.20",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
}
|
||||
8
plugins/dbgate-plugin-csv/prettier.config.js
Normal file
8
plugins/dbgate-plugin-csv/prettier.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
arrowParen: 'avoid',
|
||||
printWidth: 120,
|
||||
};
|
||||
13
plugins/dbgate-plugin-csv/src/backend/index.js
Normal file
13
plugins/dbgate-plugin-csv/src/backend/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const reader = require('./reader');
|
||||
const writer = require('./writer');
|
||||
|
||||
module.exports = {
|
||||
packageName: 'dbgate-plugin-csv',
|
||||
shellApi: {
|
||||
reader,
|
||||
writer,
|
||||
},
|
||||
initialize(dbgateEnv) {
|
||||
reader.initialize(dbgateEnv);
|
||||
},
|
||||
};
|
||||
67
plugins/dbgate-plugin-csv/src/backend/reader.js
Normal file
67
plugins/dbgate-plugin-csv/src/backend/reader.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const zipObject = require('lodash/zipObject');
|
||||
const csv = require('csv');
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
|
||||
let dbgateApi;
|
||||
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 = {
|
||||
__isStreamHeader: true,
|
||||
columns: chunk.map((columnName) => ({ columnName })),
|
||||
};
|
||||
this.push(this.structure);
|
||||
} else {
|
||||
this.structure = {
|
||||
__isStreamHeader: true,
|
||||
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 reader({ 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 : undefined,
|
||||
});
|
||||
const downloadedFile = await dbgateApi.download(fileName);
|
||||
const fileStream = fs.createReadStream(downloadedFile, encoding);
|
||||
const csvPrepare = new CsvPrepareStream({ header });
|
||||
fileStream.pipe(csvStream);
|
||||
csvStream.pipe(csvPrepare);
|
||||
return csvPrepare;
|
||||
}
|
||||
|
||||
reader.initialize = (dbgateEnv) => {
|
||||
dbgateApi = dbgateEnv.dbgateApi;
|
||||
};
|
||||
|
||||
module.exports = reader;
|
||||
36
plugins/dbgate-plugin-csv/src/backend/writer.js
Normal file
36
plugins/dbgate-plugin-csv/src/backend/writer.js
Normal file
@@ -0,0 +1,36 @@
|
||||
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 writer({ 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 = writer;
|
||||
46
plugins/dbgate-plugin-csv/src/frontend/index.js
Normal file
46
plugins/dbgate-plugin-csv/src/frontend/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const fileFormat = {
|
||||
packageName: 'dbgate-plugin-csv',
|
||||
// file format identifier
|
||||
storageType: 'csv',
|
||||
// file extension without leading dot
|
||||
extension: 'csv',
|
||||
// human readable file format name
|
||||
name: 'CSV',
|
||||
// function name from backend, which contains reader factory, postfixed by package name
|
||||
readerFunc: 'reader@dbgate-plugin-csv',
|
||||
// function name from backend, which contains writer factory, postfixed by package name
|
||||
writerFunc: 'writer@dbgate-plugin-csv',
|
||||
// optional list of format arguments, which can be edited from UI
|
||||
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 {
|
||||
fileFormats: [fileFormat],
|
||||
};
|
||||
23
plugins/dbgate-plugin-csv/webpack-backend.config.js
Normal file
23
plugins/dbgate-plugin-csv/webpack-backend.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
|
||||
var config = {
|
||||
context: __dirname + '/src/backend',
|
||||
|
||||
entry: {
|
||||
app: './index.js',
|
||||
},
|
||||
target: 'node',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'backend.js',
|
||||
libraryTarget: 'commonjs2',
|
||||
},
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
24
plugins/dbgate-plugin-csv/webpack-frontend.config.js
Normal file
24
plugins/dbgate-plugin-csv/webpack-frontend.config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
var webpack = require("webpack");
|
||||
var path = require("path");
|
||||
|
||||
var config = {
|
||||
context: __dirname + "/src/frontend",
|
||||
|
||||
entry: {
|
||||
app: "./index.js",
|
||||
},
|
||||
target: "web",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "frontend.js",
|
||||
libraryTarget: "var",
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
21
plugins/dbgate-plugin-excel/LICENSE
Normal file
21
plugins/dbgate-plugin-excel/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Jan Prochazka
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
52
plugins/dbgate-plugin-excel/README.md
Normal file
52
plugins/dbgate-plugin-excel/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://paypal.me/JanProchazkaCz/30eur)
|
||||
[](https://www.npmjs.com/package/dbgate-plugin-excel)
|
||||
|
||||
# dbgate-plugin-excel
|
||||
|
||||
MS Excel import/export plugin for DbGate
|
||||
|
||||
|
||||
## Usage without DbGate
|
||||
|
||||
Export from fake object reader into MS Excel file. Fake object file can be replaced with other reader/writer factory functions, as described in
|
||||
[dbgate-api package](https://www.npmjs.com/package/dbgate-api)
|
||||
|
||||
```javascript
|
||||
const dbgateApi = require('dbgate-api');
|
||||
const dbgatePluginExcel = require("dbgate-plugin-excel");
|
||||
|
||||
dbgateApi.registerPlugins(dbgatePluginExcel);
|
||||
|
||||
|
||||
async function run() {
|
||||
const reader = await dbgateApi.fakeObjectReader();
|
||||
const writer = await dbgatePluginExcel.shellApi.writer({ fileName: 'myfile1.xlsx', sheetName: 'Sheet 1' });
|
||||
await dbgateApi.copyStream(reader, writer);
|
||||
console.log('Finished job script');
|
||||
}
|
||||
dbgateApi.runScript(run);
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Factory functions
|
||||
|
||||
### shellApi.reader
|
||||
Reads tabular data from one sheet in MS Excel file.
|
||||
```js
|
||||
const reader = await dbgatePluginExcel.shellApi.reader({
|
||||
fileName: 'test.xlsx',
|
||||
sheetName: 'Album',
|
||||
limitRows: null
|
||||
});
|
||||
```
|
||||
|
||||
### shellApi.writer
|
||||
Writes tabular data into MS excel file. There could be more writes into the some file in one script, if property sheetName is different.
|
||||
```js
|
||||
const reader = await dbgatePluginExcel.shellApi.writer({
|
||||
fileName: 'test.xlsx',
|
||||
sheetName: 'Album',
|
||||
});
|
||||
```
|
||||
33
plugins/dbgate-plugin-excel/icon.svg
Normal file
33
plugins/dbgate-plugin-excel/icon.svg
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path style="fill:#ECEFF1;" d="M496,432.011H272c-8.832,0-16-7.168-16-16s0-311.168,0-320s7.168-16,16-16h224
|
||||
c8.832,0,16,7.168,16,16v320C512,424.843,504.832,432.011,496,432.011z"/>
|
||||
<g>
|
||||
<path style="fill:#388E3C;" d="M336,176.011h-64c-8.832,0-16-7.168-16-16s7.168-16,16-16h64c8.832,0,16,7.168,16,16
|
||||
S344.832,176.011,336,176.011z"/>
|
||||
<path style="fill:#388E3C;" d="M336,240.011h-64c-8.832,0-16-7.168-16-16s7.168-16,16-16h64c8.832,0,16,7.168,16,16
|
||||
S344.832,240.011,336,240.011z"/>
|
||||
<path style="fill:#388E3C;" d="M336,304.011h-64c-8.832,0-16-7.168-16-16s7.168-16,16-16h64c8.832,0,16,7.168,16,16
|
||||
S344.832,304.011,336,304.011z"/>
|
||||
<path style="fill:#388E3C;" d="M336,368.011h-64c-8.832,0-16-7.168-16-16s7.168-16,16-16h64c8.832,0,16,7.168,16,16
|
||||
S344.832,368.011,336,368.011z"/>
|
||||
<path style="fill:#388E3C;" d="M432,176.011h-32c-8.832,0-16-7.168-16-16s7.168-16,16-16h32c8.832,0,16,7.168,16,16
|
||||
S440.832,176.011,432,176.011z"/>
|
||||
<path style="fill:#388E3C;" d="M432,240.011h-32c-8.832,0-16-7.168-16-16s7.168-16,16-16h32c8.832,0,16,7.168,16,16
|
||||
S440.832,240.011,432,240.011z"/>
|
||||
<path style="fill:#388E3C;" d="M432,304.011h-32c-8.832,0-16-7.168-16-16s7.168-16,16-16h32c8.832,0,16,7.168,16,16
|
||||
S440.832,304.011,432,304.011z"/>
|
||||
<path style="fill:#388E3C;" d="M432,368.011h-32c-8.832,0-16-7.168-16-16s7.168-16,16-16h32c8.832,0,16,7.168,16,16
|
||||
S440.832,368.011,432,368.011z"/>
|
||||
</g>
|
||||
<path style="fill:#2E7D32;" d="M282.208,19.691c-3.648-3.04-8.544-4.352-13.152-3.392l-256,48C5.472,65.707,0,72.299,0,80.011v352
|
||||
c0,7.68,5.472,14.304,13.056,15.712l256,48c0.96,0.192,1.952,0.288,2.944,0.288c3.712,0,7.328-1.28,10.208-3.68
|
||||
c3.68-3.04,5.792-7.584,5.792-12.32v-448C288,27.243,285.888,22.731,282.208,19.691z"/>
|
||||
<path style="fill:#FAFAFA;" d="M220.032,309.483l-50.592-57.824l51.168-65.792c5.44-6.976,4.16-17.024-2.784-22.464
|
||||
c-6.944-5.44-16.992-4.16-22.464,2.784l-47.392,60.928l-39.936-45.632c-5.856-6.72-15.968-7.328-22.56-1.504
|
||||
c-6.656,5.824-7.328,15.936-1.504,22.56l44,50.304L83.36,310.187c-5.44,6.976-4.16,17.024,2.784,22.464
|
||||
c2.944,2.272,6.432,3.36,9.856,3.36c4.768,0,9.472-2.112,12.64-6.176l40.8-52.48l46.528,53.152
|
||||
c3.168,3.648,7.584,5.504,12.032,5.504c3.744,0,7.488-1.312,10.528-3.968C225.184,326.219,225.856,316.107,220.032,309.483z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
39
plugins/dbgate-plugin-excel/package.json
Normal file
39
plugins/dbgate-plugin-excel/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "dbgate-plugin-excel",
|
||||
"main": "dist/backend.js",
|
||||
"version": "1.0.8",
|
||||
"homepage": "https://github.com/dbgate/dbgate-plugin-excel",
|
||||
"description": "MS Excel import/export plugin for DbGate",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate-plugin-excel.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "GPL",
|
||||
"keywords": [
|
||||
"excel",
|
||||
"import",
|
||||
"export",
|
||||
"dbgate",
|
||||
"dbgateplugin"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build:frontend": "webpack --config webpack-frontend.config",
|
||||
"build:backend": "webpack --config webpack-backend.config.js",
|
||||
"build": "yarn build:frontend && yarn build:backend",
|
||||
"plugin": "yarn build && dbgate-plugin dbgate-plugin-excel",
|
||||
"plugout": "dbgate-plugout dbgate-plugin-excel",
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dbgate-plugin-tools": "^1.0.0",
|
||||
"lodash": "^4.17.20",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"xlsx": "^0.16.8"
|
||||
}
|
||||
}
|
||||
8
plugins/dbgate-plugin-excel/prettier.config.js
Normal file
8
plugins/dbgate-plugin-excel/prettier.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
arrowParen: 'avoid',
|
||||
printWidth: 120,
|
||||
};
|
||||
26
plugins/dbgate-plugin-excel/src/backend/index.js
Normal file
26
plugins/dbgate-plugin-excel/src/backend/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const xlsx = require('xlsx');
|
||||
const reader = require('./reader');
|
||||
const writer = require('./writer');
|
||||
|
||||
let dbgateApi;
|
||||
|
||||
module.exports = {
|
||||
packageName: 'dbgate-plugin-excel',
|
||||
shellApi: {
|
||||
reader,
|
||||
writer,
|
||||
},
|
||||
|
||||
commands: {
|
||||
analyse: async ({ fileName }) => {
|
||||
const downloadedFile = await dbgateApi.download(fileName);
|
||||
const workbook = xlsx.readFile(downloadedFile, { bookSheets: true });
|
||||
return workbook.SheetNames;
|
||||
},
|
||||
},
|
||||
initialize(dbgateEnv) {
|
||||
dbgateApi = dbgateEnv.dbgateApi;
|
||||
writer.initialize(dbgateEnv);
|
||||
reader.initialize(dbgateEnv);
|
||||
},
|
||||
};
|
||||
67
plugins/dbgate-plugin-excel/src/backend/reader.js
Normal file
67
plugins/dbgate-plugin-excel/src/backend/reader.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const xlsx = require('xlsx');
|
||||
const stream = require('stream');
|
||||
const _ = require('lodash');
|
||||
|
||||
const loadedWorkbooks = {};
|
||||
let dbgateApi;
|
||||
|
||||
async function loadWorkbook(fileName) {
|
||||
let workbook = loadedWorkbooks[fileName];
|
||||
if (workbook) return workbook;
|
||||
console.log(`Loading excel ${fileName}`);
|
||||
const downloadedFile = await dbgateApi.download(fileName);
|
||||
workbook = xlsx.readFile(downloadedFile);
|
||||
loadedWorkbooks[fileName] = workbook;
|
||||
return workbook;
|
||||
}
|
||||
|
||||
async function waitForDrain(stream) {
|
||||
return new Promise((resolve) => {
|
||||
stream.once('drain', () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function reader({ fileName, sheetName, limitRows = undefined }) {
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
});
|
||||
|
||||
const workbook = await loadWorkbook(fileName);
|
||||
const sheet = workbook.Sheets[sheetName];
|
||||
|
||||
const rows = xlsx.utils.sheet_to_json(sheet, {
|
||||
header: 1,
|
||||
blankrows: false,
|
||||
});
|
||||
const header = rows[0];
|
||||
const structure = {
|
||||
__isStreamHeader: true,
|
||||
columns: _.range(header.length).map((index) => ({ columnName: header[index] })),
|
||||
};
|
||||
if (!pass.write(structure)) await waitForDrain(pass);
|
||||
|
||||
const sendAsync = async () => {
|
||||
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;
|
||||
if (!pass.write(rowData)) await waitForDrain(pass);
|
||||
}
|
||||
pass.end();
|
||||
};
|
||||
|
||||
// don't wait for sending
|
||||
sendAsync();
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
reader.initialize = (dbgateEnv) => {
|
||||
dbgateApi = dbgateEnv.dbgateApi;
|
||||
};
|
||||
|
||||
module.exports = reader;
|
||||
57
plugins/dbgate-plugin-excel/src/backend/writer.js
Normal file
57
plugins/dbgate-plugin-excel/src/backend/writer.js
Normal file
@@ -0,0 +1,57 @@
|
||||
const xlsx = require('xlsx');
|
||||
const stream = require('stream');
|
||||
|
||||
const writingWorkbooks = {};
|
||||
|
||||
async function saveExcelFiles() {
|
||||
for (const file in writingWorkbooks) {
|
||||
xlsx.writeFile(writingWorkbooks[file], file);
|
||||
}
|
||||
}
|
||||
|
||||
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 writer({ fileName, sheetName }) {
|
||||
return new ExcelSheetWriterStream({
|
||||
fileName,
|
||||
sheetName,
|
||||
});
|
||||
}
|
||||
|
||||
writer.initialize = ({ dbgateApi }) => {
|
||||
dbgateApi.finalizer.register(saveExcelFiles);
|
||||
};
|
||||
|
||||
module.exports = writer;
|
||||
68
plugins/dbgate-plugin-excel/src/frontend/index.js
Normal file
68
plugins/dbgate-plugin-excel/src/frontend/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
let axios;
|
||||
|
||||
function initialize(dbgateEnv) {
|
||||
axios = dbgateEnv.axios;
|
||||
}
|
||||
|
||||
const fileFormat = {
|
||||
packageName: 'dbgate-plugin-excel',
|
||||
// file format identifier
|
||||
storageType: 'excel',
|
||||
// file extension without leading dot
|
||||
extension: 'xlsx',
|
||||
// human readable file format name
|
||||
name: 'MS Excel',
|
||||
// function name from backend, which contains reader factory, postfixed by package name
|
||||
readerFunc: 'reader@dbgate-plugin-excel',
|
||||
// function name from backend, which contains writer factory, postfixed by package name
|
||||
writerFunc: 'writer@dbgate-plugin-excel',
|
||||
|
||||
addFileToSourceList: async ({ fileName }, newSources, newValues) => {
|
||||
const resp = await axios.post('plugins/command', {
|
||||
command: 'analyse',
|
||||
packageName: 'dbgate-plugin-excel',
|
||||
args: {
|
||||
fileName,
|
||||
},
|
||||
});
|
||||
const sheetNames = resp.data;
|
||||
for (const sheetName of sheetNames) {
|
||||
newSources.push(sheetName);
|
||||
newValues[`sourceFile_${sheetName}`] = {
|
||||
fileName,
|
||||
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 {
|
||||
fileFormats: [fileFormat],
|
||||
initialize,
|
||||
};
|
||||
23
plugins/dbgate-plugin-excel/webpack-backend.config.js
Normal file
23
plugins/dbgate-plugin-excel/webpack-backend.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
|
||||
var config = {
|
||||
context: __dirname + '/src/backend',
|
||||
|
||||
entry: {
|
||||
app: './index.js',
|
||||
},
|
||||
target: 'node',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'backend.js',
|
||||
libraryTarget: 'commonjs2',
|
||||
},
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
24
plugins/dbgate-plugin-excel/webpack-frontend.config.js
Normal file
24
plugins/dbgate-plugin-excel/webpack-frontend.config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
var webpack = require("webpack");
|
||||
var path = require("path");
|
||||
|
||||
var config = {
|
||||
context: __dirname + "/src/frontend",
|
||||
|
||||
entry: {
|
||||
app: "./index.js",
|
||||
},
|
||||
target: "web",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "frontend.js",
|
||||
libraryTarget: "var",
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
21
plugins/dbgate-plugin-mongo/LICENSE
Normal file
21
plugins/dbgate-plugin-mongo/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 DbGate
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
6
plugins/dbgate-plugin-mongo/README.md
Normal file
6
plugins/dbgate-plugin-mongo/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://www.npmjs.com/package/dbgate-plugin-mongo)
|
||||
|
||||
# dbgate-plugin-mongo
|
||||
|
||||
Use DbGate for install of this plugin
|
||||
6
plugins/dbgate-plugin-mongo/icon.svg
Normal file
6
plugins/dbgate-plugin-mongo/icon.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg height="2500" viewBox="8.738 -5.03622834 17.45992422 39.40619484" width="2500"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m15.9.087.854 1.604c.192.296.4.558.645.802a22.406 22.406 0 0 1 2.004 2.266c1.447 1.9 2.423 4.01 3.12 6.292.418 1.394.645 2.824.662 4.27.07 4.323-1.412 8.035-4.4 11.12a12.7 12.7 0 0 1 -1.57 1.342c-.296 0-.436-.227-.558-.436a3.589 3.589 0 0 1 -.436-1.255c-.105-.523-.174-1.046-.14-1.586v-.244c-.024-.052-.285-24.052-.181-24.175z" fill="#599636"/>
|
||||
<path d="m15.9.034c-.035-.07-.07-.017-.105.017.017.35-.105.662-.296.96-.21.296-.488.523-.767.767-1.55 1.342-2.77 2.963-3.747 4.776-1.3 2.44-1.97 5.055-2.16 7.808-.087.993.314 4.497.627 5.508.854 2.684 2.388 4.933 4.375 6.885.488.47 1.01.906 1.55 1.325.157 0 .174-.14.21-.244a4.78 4.78 0 0 0 .157-.68l.35-2.614z" fill="#6cac48"/>
|
||||
<path d="m16.754 28.845c.035-.4.227-.732.436-1.063-.21-.087-.366-.26-.488-.453a3.235 3.235 0 0 1 -.26-.575c-.244-.732-.296-1.5-.366-2.248v-.453c-.087.07-.105.662-.105.75a17.37 17.37 0 0 1 -.314 2.353c-.052.314-.087.627-.28.906 0 .035 0 .07.017.122.314.924.4 1.865.453 2.824v.35c0 .418-.017.33.33.47.14.052.296.07.436.174.105 0 .122-.087.122-.157l-.052-.575v-1.604c-.017-.28.035-.558.07-.82z" fill="#c2bfbf"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
36
plugins/dbgate-plugin-mongo/package.json
Normal file
36
plugins/dbgate-plugin-mongo/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "dbgate-plugin-mongo",
|
||||
"main": "dist/backend.js",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"author": "Jan Prochazka",
|
||||
"homepage": "https://github.com/dbgate/dbgate-plugin-mongo",
|
||||
"description": "MongoDB connect plugin for DbGate",
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"keywords": [
|
||||
"dbgate",
|
||||
"dbgateplugin",
|
||||
"mongo",
|
||||
"mongodb"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build:frontend": "webpack --config webpack-frontend.config",
|
||||
"build:backend": "webpack --config webpack-backend.config.js",
|
||||
"build": "yarn build:frontend && yarn build:backend",
|
||||
"plugin": "yarn build && yarn pack && dbgate-plugin dbgate-plugin-mongo",
|
||||
"plugout": "dbgate-plugout dbgate-plugin-mongo",
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"byline": "^5.0.0",
|
||||
"dbgate-plugin-tools": "^1.0.4",
|
||||
"dbgate-tools": "^4.1.0-rc.1",
|
||||
"is-promise": "^4.0.0",
|
||||
"mongodb": "^3.6.5",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
}
|
||||
8
plugins/dbgate-plugin-mongo/prettier.config.js
Normal file
8
plugins/dbgate-plugin-mongo/prettier.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
arrowParen: 'avoid',
|
||||
printWidth: 120,
|
||||
};
|
||||
24
plugins/dbgate-plugin-mongo/src/backend/Analyser.js
Normal file
24
plugins/dbgate-plugin-mongo/src/backend/Analyser.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||
|
||||
class Analyser extends DatabaseAnalyser {
|
||||
constructor(pool, driver) {
|
||||
super(pool, driver);
|
||||
}
|
||||
|
||||
async _runAnalysis() {
|
||||
const collections = await this.pool.__getDatabase().listCollections().toArray();
|
||||
|
||||
const res = this.mergeAnalyseResult(
|
||||
{
|
||||
collections: collections.map((x) => ({
|
||||
pureName: x.name,
|
||||
})),
|
||||
},
|
||||
(x) => x.pureName
|
||||
);
|
||||
// console.log('MERGED', res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Analyser;
|
||||
@@ -0,0 +1,58 @@
|
||||
export function createBulkInsertStream(driver, stream, pool, name, options) {
|
||||
const collectionName = name.pureName;
|
||||
const db = pool.__getDatabase();
|
||||
|
||||
const writable = new stream.Writable({
|
||||
objectMode: true,
|
||||
});
|
||||
|
||||
writable.buffer = [];
|
||||
writable.wasHeader = false;
|
||||
|
||||
writable.addRow = (row) => {
|
||||
if (!writable.wasHeader) {
|
||||
writable.wasHeader = true;
|
||||
if (row.__isStreamHeader ||
|
||||
// TODO remove isArray test
|
||||
Array.isArray(row.columns)) return;
|
||||
}
|
||||
writable.buffer.push(row);
|
||||
};
|
||||
|
||||
writable.checkStructure = async () => {
|
||||
if (options.dropIfExists || options.truncate) {
|
||||
console.log(`Dropping collection ${collectionName}`);
|
||||
await db.collection(collectionName).drop();
|
||||
}
|
||||
if (options.truncate) {
|
||||
console.log(`Truncating collection ${collectionName}`);
|
||||
await db.collection(collectionName).deleteMany({});
|
||||
}
|
||||
};
|
||||
|
||||
writable.send = async () => {
|
||||
const rows = writable.buffer;
|
||||
writable.buffer = [];
|
||||
|
||||
await db.collection(collectionName).insertMany(rows);
|
||||
};
|
||||
|
||||
writable.sendIfFull = async () => {
|
||||
if (writable.buffer.length > 100) {
|
||||
await writable.send();
|
||||
}
|
||||
};
|
||||
|
||||
writable._write = async (chunk, encoding, callback) => {
|
||||
writable.addRow(chunk);
|
||||
await writable.sendIfFull();
|
||||
callback();
|
||||
};
|
||||
|
||||
writable._final = async (callback) => {
|
||||
await writable.send();
|
||||
callback();
|
||||
};
|
||||
|
||||
return writable;
|
||||
}
|
||||
260
plugins/dbgate-plugin-mongo/src/backend/driver.js
Normal file
260
plugins/dbgate-plugin-mongo/src/backend/driver.js
Normal file
@@ -0,0 +1,260 @@
|
||||
const _ = require('lodash');
|
||||
const stream = require('stream');
|
||||
const isPromise = require('is-promise');
|
||||
const driverBase = require('../frontend/driver');
|
||||
const Analyser = require('./Analyser');
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
const ObjectId = require('mongodb').ObjectId;
|
||||
const Cursor = require('mongodb').Cursor;
|
||||
const { createBulkInsertStream } = require('./createBulkInsertStream');
|
||||
|
||||
function readCursor(cursor, options) {
|
||||
return new Promise((resolve) => {
|
||||
options.recordset({ __isDynamicStructure: true });
|
||||
|
||||
cursor.on('data', (data) => options.row(data));
|
||||
cursor.on('end', () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
const mongoIdRegex = /^[0-9a-f]{24}$/;
|
||||
function convertCondition(condition) {
|
||||
if (condition && _.isString(condition._id) && condition._id.match(mongoIdRegex)) {
|
||||
return {
|
||||
_id: ObjectId(condition._id),
|
||||
};
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
||||
function findArrayResult(resValue) {
|
||||
if (!_.isPlainObject(resValue)) return null;
|
||||
const arrays = _.values(resValue).filter((x) => _.isArray(x));
|
||||
if (arrays.length == 1) return arrays[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getScriptableDb(pool) {
|
||||
const db = pool.__getDatabase();
|
||||
const collections = await db.listCollections().toArray();
|
||||
for (const collection of collections) {
|
||||
db[collection.name] = db.collection(collection.name);
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
analyserClass: Analyser,
|
||||
async connect({ server, port, user, password, database, useDatabaseUrl, databaseUrl, ssl }) {
|
||||
// let mongoUrl = databaseUrl;
|
||||
// if (!useDatabaseUrl) {
|
||||
// mongoUrl = user ? `mongodb://${user}:${password}@${server}:${port}` : `mongodb://${server}:${port}`;
|
||||
// if (database) mongoUrl += '/' + database;
|
||||
// }
|
||||
const mongoUrl = useDatabaseUrl
|
||||
? databaseUrl
|
||||
: user
|
||||
? `mongodb://${user}:${password}@${server}:${port}`
|
||||
: `mongodb://${server}:${port}`;
|
||||
|
||||
const options = {};
|
||||
if (ssl) {
|
||||
options.tls = true;
|
||||
options.tlsCAFile = ssl.ca;
|
||||
options.tlsCertificateKeyFile = ssl.cert || ssl.key;
|
||||
options.tlsCertificateKeyFilePassword = ssl.password;
|
||||
options.tlsAllowInvalidCertificates = !ssl.rejectUnauthorized;
|
||||
}
|
||||
|
||||
const pool = new MongoClient(mongoUrl, options);
|
||||
await pool.connect();
|
||||
// const pool = await MongoClient.connect(mongoUrl);
|
||||
pool.__getDatabase = database ? () => pool.db(database) : () => pool.db();
|
||||
pool.__databaseName = database;
|
||||
return pool;
|
||||
},
|
||||
// @ts-ignore
|
||||
async query(pool, sql) {
|
||||
return {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
},
|
||||
async stream(pool, sql, options) {
|
||||
let func;
|
||||
try {
|
||||
func = eval(`(db,ObjectId) => ${sql}`);
|
||||
} catch (err) {
|
||||
options.info({
|
||||
message: 'Error compiling expression: ' + err.message,
|
||||
time: new Date(),
|
||||
severity: 'error',
|
||||
});
|
||||
options.done();
|
||||
return;
|
||||
}
|
||||
const db = await getScriptableDb(pool);
|
||||
|
||||
let exprValue;
|
||||
try {
|
||||
exprValue = func(db, ObjectId);
|
||||
} catch (err) {
|
||||
options.info({
|
||||
message: 'Error evaluating expression: ' + err.message,
|
||||
time: new Date(),
|
||||
severity: 'error',
|
||||
});
|
||||
options.done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (exprValue instanceof Cursor) {
|
||||
await readCursor(exprValue, options);
|
||||
} else if (isPromise(exprValue)) {
|
||||
try {
|
||||
const resValue = await exprValue;
|
||||
|
||||
options.info({
|
||||
message: 'Command succesfully executed',
|
||||
time: new Date(),
|
||||
severity: 'info',
|
||||
});
|
||||
options.info({
|
||||
message: JSON.stringify(resValue),
|
||||
time: new Date(),
|
||||
severity: 'info',
|
||||
});
|
||||
|
||||
const arrayRes = findArrayResult(resValue);
|
||||
if (arrayRes) {
|
||||
options.recordset({ __isDynamicStructure: true });
|
||||
for (const row of arrayRes) {
|
||||
options.row(row);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
options.info({
|
||||
message: 'Error when running command: ' + err.message,
|
||||
time: new Date(),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
options.done();
|
||||
},
|
||||
async readQuery(pool, sql, structure) {
|
||||
try {
|
||||
const json = JSON.parse(sql);
|
||||
if (json && json.pureName) {
|
||||
sql = `db.${json.pureName}.find()`;
|
||||
}
|
||||
} catch (err) {
|
||||
// query is not JSON serialized collection name
|
||||
}
|
||||
|
||||
// const pass = new stream.PassThrough({
|
||||
// objectMode: true,
|
||||
// highWaterMark: 100,
|
||||
// });
|
||||
|
||||
func = eval(`(db,ObjectId) => ${sql}`);
|
||||
const db = await getScriptableDb(pool);
|
||||
exprValue = func(db, ObjectId);
|
||||
|
||||
// return directly stream without header row
|
||||
return exprValue;
|
||||
|
||||
// pass.write(structure || { __isDynamicStructure: true });
|
||||
// exprValue.on('data', (row) => pass.write(row));
|
||||
// exprValue.on('end', () => pass.end());
|
||||
|
||||
// return pass;
|
||||
},
|
||||
async writeTable(pool, name, options) {
|
||||
return createBulkInsertStream(this, stream, pool, name, options);
|
||||
},
|
||||
async getVersion(pool) {
|
||||
const status = await pool.__getDatabase().admin().serverInfo();
|
||||
return status;
|
||||
},
|
||||
async listDatabases(pool) {
|
||||
const res = await pool.__getDatabase().admin().listDatabases();
|
||||
return res.databases;
|
||||
},
|
||||
async readCollection(pool, options) {
|
||||
try {
|
||||
const collection = pool.__getDatabase().collection(options.pureName);
|
||||
if (options.countDocuments) {
|
||||
const count = await collection.countDocuments(options.condition || {});
|
||||
return { count };
|
||||
} else {
|
||||
let cursor = await collection.find(options.condition || {});
|
||||
if (options.sort) cursor = cursor.sort(options.sort);
|
||||
if (options.skip) cursor = cursor.skip(options.skip);
|
||||
if (options.limit) cursor = cursor.limit(options.limit);
|
||||
const rows = await cursor.toArray();
|
||||
return { rows };
|
||||
}
|
||||
} catch (err) {
|
||||
return { errorMessage: err.message };
|
||||
}
|
||||
},
|
||||
async updateCollection(pool, changeSet) {
|
||||
const res = {
|
||||
inserted: [],
|
||||
updated: [],
|
||||
deleted: [],
|
||||
replaced: [],
|
||||
};
|
||||
try {
|
||||
const db = pool.__getDatabase();
|
||||
for (const insert of changeSet.inserts) {
|
||||
const collection = db.collection(insert.pureName);
|
||||
const document = {
|
||||
...insert.document,
|
||||
...insert.fields,
|
||||
};
|
||||
const resdoc = await collection.insert(document);
|
||||
res.inserted.push(resdoc._id);
|
||||
}
|
||||
for (const update of changeSet.updates) {
|
||||
const collection = db.collection(update.pureName);
|
||||
if (update.document) {
|
||||
const document = {
|
||||
...update.document,
|
||||
...update.fields,
|
||||
};
|
||||
const doc = await collection.findOne(convertCondition(update.condition));
|
||||
if (doc) {
|
||||
const resdoc = await collection.replaceOne(convertCondition(update.condition), {
|
||||
...document,
|
||||
_id: doc._id,
|
||||
});
|
||||
res.replaced.push(resdoc._id);
|
||||
}
|
||||
} else {
|
||||
const resdoc = await collection.updateOne(convertCondition(update.condition), { $set: update.fields });
|
||||
res.updated.push(resdoc._id);
|
||||
}
|
||||
}
|
||||
for (const del of changeSet.deletes) {
|
||||
const collection = db.collection(del.pureName);
|
||||
const resdoc = await collection.deleteOne(convertCondition(del.condition));
|
||||
res.deleted.push(resdoc._id);
|
||||
}
|
||||
return res;
|
||||
} catch (err) {
|
||||
return { errorMessage: err.message };
|
||||
}
|
||||
},
|
||||
|
||||
async createDatabase(pool, name) {
|
||||
const db = pool.db(name);
|
||||
await db.createCollection('collection1');
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
6
plugins/dbgate-plugin-mongo/src/backend/index.js
Normal file
6
plugins/dbgate-plugin-mongo/src/backend/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const driver = require('./driver');
|
||||
|
||||
module.exports = {
|
||||
packageName: 'dbgate-plugin-mongo',
|
||||
driver,
|
||||
};
|
||||
6
plugins/dbgate-plugin-mongo/src/frontend/Dumper.js
Normal file
6
plugins/dbgate-plugin-mongo/src/frontend/Dumper.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { SqlDumper } = require('dbgate-tools');
|
||||
|
||||
class Dumper extends SqlDumper {
|
||||
}
|
||||
|
||||
module.exports = Dumper;
|
||||
76
plugins/dbgate-plugin-mongo/src/frontend/driver.js
Normal file
76
plugins/dbgate-plugin-mongo/src/frontend/driver.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const { driverBase } = require('dbgate-tools');
|
||||
const Dumper = require('./Dumper');
|
||||
|
||||
const mongoIdRegex = /^[0-9a-f]{24}$/;
|
||||
|
||||
function getConditionPreview(condition) {
|
||||
if (condition && _.isString(condition._id) && condition._id.match(mongoIdRegex)) {
|
||||
return `{ _id: ObjectId('${condition._id}') }`;
|
||||
}
|
||||
return JSON.stringify(condition);
|
||||
}
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
const dialect = {
|
||||
limitSelect: true,
|
||||
rangeSelect: true,
|
||||
offsetFetchRangeSyntax: true,
|
||||
stringEscapeChar: "'",
|
||||
fallbackDataType: 'nvarchar(max)',
|
||||
nosql: true,
|
||||
quoteIdentifier(s) {
|
||||
return `[${s}]`;
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
dumperClass: Dumper,
|
||||
dialect,
|
||||
engine: 'mongo@dbgate-plugin-mongo',
|
||||
title: 'MongoDB',
|
||||
defaultPort: 27017,
|
||||
supportsDatabaseUrl: true,
|
||||
databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname',
|
||||
|
||||
getCollectionUpdateScript(changeSet) {
|
||||
let res = '';
|
||||
for (const insert of changeSet.inserts) {
|
||||
res += `db.${insert.pureName}.insert(${JSON.stringify(
|
||||
{
|
||||
...insert.document,
|
||||
...insert.fields,
|
||||
},
|
||||
undefined,
|
||||
2
|
||||
)});\n`;
|
||||
}
|
||||
for (const update of changeSet.updates) {
|
||||
if (update.document) {
|
||||
res += `db.${update.pureName}.replaceOne(${getConditionPreview(update.condition)}, ${JSON.stringify(
|
||||
{
|
||||
...update.document,
|
||||
...update.fields,
|
||||
},
|
||||
undefined,
|
||||
2
|
||||
)});\n`;
|
||||
} else {
|
||||
res += `db.${update.pureName}.updateOne(${getConditionPreview(update.condition)}, ${JSON.stringify(
|
||||
{
|
||||
$set: update.fields,
|
||||
},
|
||||
undefined,
|
||||
2
|
||||
)});\n`;
|
||||
}
|
||||
}
|
||||
for (const del of changeSet.deletes) {
|
||||
res += `db.${del.pureName}.deleteOne(${getConditionPreview(del.condition)});\n`;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
6
plugins/dbgate-plugin-mongo/src/frontend/index.js
Normal file
6
plugins/dbgate-plugin-mongo/src/frontend/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import driver from './driver';
|
||||
|
||||
export default {
|
||||
packageName: 'dbgate-plugin-mongo',
|
||||
driver,
|
||||
};
|
||||
23
plugins/dbgate-plugin-mongo/webpack-backend.config.js
Normal file
23
plugins/dbgate-plugin-mongo/webpack-backend.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
|
||||
var config = {
|
||||
context: __dirname + '/src/backend',
|
||||
|
||||
entry: {
|
||||
app: './index.js',
|
||||
},
|
||||
target: 'node',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'backend.js',
|
||||
libraryTarget: 'commonjs2',
|
||||
},
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
24
plugins/dbgate-plugin-mongo/webpack-frontend.config.js
Normal file
24
plugins/dbgate-plugin-mongo/webpack-frontend.config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
var webpack = require("webpack");
|
||||
var path = require("path");
|
||||
|
||||
var config = {
|
||||
context: __dirname + "/src/frontend",
|
||||
|
||||
entry: {
|
||||
app: "./index.js",
|
||||
},
|
||||
target: "web",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "frontend.js",
|
||||
libraryTarget: "var",
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
21
plugins/dbgate-plugin-mssql/LICENSE
Normal file
21
plugins/dbgate-plugin-mssql/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Jan Prochazka
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
7
plugins/dbgate-plugin-mssql/README.md
Normal file
7
plugins/dbgate-plugin-mssql/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://paypal.me/JanProchazkaCz/30eur)
|
||||
[](https://www.npmjs.com/package/dbgate-plugin-mssql)
|
||||
|
||||
# dbgate-plugin-mssql
|
||||
|
||||
MS SQL connector plugin for DbGate
|
||||
20
plugins/dbgate-plugin-mssql/icon.svg
Normal file
20
plugins/dbgate-plugin-mssql/icon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 24 KiB |
39
plugins/dbgate-plugin-mssql/package.json
Normal file
39
plugins/dbgate-plugin-mssql/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "dbgate-plugin-mssql",
|
||||
"main": "dist/backend.js",
|
||||
"version": "1.2.2",
|
||||
"homepage": "https://github.com/dbgate/dbgate-plugin-mssql",
|
||||
"description": "MS SQL connect plugin for DbGate",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate-plugin-mssql.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "GPL",
|
||||
"keywords": [
|
||||
"sql",
|
||||
"mssql",
|
||||
"dbgate",
|
||||
"dbgateplugin"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build:frontend": "webpack --config webpack-frontend.config",
|
||||
"build:backend": "webpack --config webpack-backend.config.js",
|
||||
"build": "yarn build:frontend && yarn build:backend",
|
||||
"prepublishOnly": "yarn build",
|
||||
"plugin": "yarn build && yarn pack && dbgate-plugin dbgate-plugin-mssql",
|
||||
"plugout": "dbgate-plugout dbgate-plugin-mssql"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async-lock": "^1.2.6",
|
||||
"dbgate-plugin-tools": "^1.0.4",
|
||||
"dbgate-tools": "^4.0.3-rc.1",
|
||||
"tedious": "^9.2.3",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
}
|
||||
9
plugins/dbgate-plugin-mssql/prettier.config.js
Normal file
9
plugins/dbgate-plugin-mssql/prettier.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
arrowParen: 'avoid',
|
||||
arrowParens: 'avoid',
|
||||
printWidth: 120,
|
||||
};
|
||||
208
plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js
Normal file
208
plugins/dbgate-plugin-mssql/src/backend/MsSqlAnalyser.js
Normal file
@@ -0,0 +1,208 @@
|
||||
const fp = require('lodash/fp');
|
||||
const _ = require('lodash');
|
||||
const sql = require('./sql');
|
||||
|
||||
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,34 @@
|
||||
const { createBulkInsertStreamBase } = require('dbgate-tools');
|
||||
|
||||
function runBulkInsertBatch(pool, tableName, writable, rows) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tableMgr = pool.tableMgr();
|
||||
tableMgr.bind(tableName, bulkMgr => {
|
||||
bulkMgr.insertRows(rows, err => {
|
||||
if (err) reject(err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('dbgate-types').EngineDriver} driver
|
||||
*/
|
||||
function createNativeBulkInsertStream(driver, 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 () => {
|
||||
const rows = writable.buffer;
|
||||
writable.buffer = [];
|
||||
|
||||
await runBulkInsertBatch(pool, fullName, writable, rows);
|
||||
};
|
||||
|
||||
return writable;
|
||||
}
|
||||
|
||||
module.exports = createNativeBulkInsertStream;
|
||||
@@ -0,0 +1,70 @@
|
||||
const { createBulkInsertStreamBase } = require('dbgate-tools');
|
||||
const tedious = require('tedious');
|
||||
const getConcreteType = require('./getConcreteType');
|
||||
|
||||
function runBulkInsertBatch(pool, tableName, writable, rows) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var options = { keepNulls: true };
|
||||
|
||||
// instantiate - provide the table where you'll be inserting to, options and a callback
|
||||
var bulkLoad = pool.newBulkLoad(tableName, options, (error, rowCount) => {
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
});
|
||||
|
||||
for (const column of writable.columnNames) {
|
||||
const tcol = writable.templateColumns.find((x) => x.columnName == column);
|
||||
|
||||
bulkLoad.addColumn(
|
||||
column,
|
||||
tcol
|
||||
? getConcreteType(tcol.driverNativeColumn.type, tcol.driverNativeColumn.dataLength)
|
||||
: tedious.TYPES.NVarChar,
|
||||
{
|
||||
nullable: tcol ? !tcol.notNull : true,
|
||||
length: tcol ? tcol.driverNativeColumn.dataLength : undefined,
|
||||
precision: tcol ? tcol.driverNativeColumn.precision : undefined,
|
||||
scale: tcol ? tcol.driverNativeColumn.scale : undefined,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
bulkLoad.addRow(row);
|
||||
}
|
||||
|
||||
pool.execBulkLoad(bulkLoad);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('dbgate-types').EngineDriver} driver
|
||||
*/
|
||||
function createTediousBulkInsertStream(driver, 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 driver.query(pool, `SELECT * FROM ${fullNameQuoted} WHERE 1=0`, {
|
||||
addDriverNativeColumn: true,
|
||||
});
|
||||
writable.templateColumns = respTemplate.columns;
|
||||
}
|
||||
|
||||
const rows = writable.buffer;
|
||||
writable.buffer = [];
|
||||
|
||||
await runBulkInsertBatch(pool, fullName, writable, rows);
|
||||
};
|
||||
|
||||
return writable;
|
||||
}
|
||||
|
||||
module.exports = createTediousBulkInsertStream;
|
||||
98
plugins/dbgate-plugin-mssql/src/backend/driver.js
Normal file
98
plugins/dbgate-plugin-mssql/src/backend/driver.js
Normal file
@@ -0,0 +1,98 @@
|
||||
const _ = require('lodash');
|
||||
const stream = require('stream');
|
||||
const driverBase = require('../frontend/driver');
|
||||
const MsSqlAnalyser = require('./MsSqlAnalyser');
|
||||
const createTediousBulkInsertStream = require('./createTediousBulkInsertStream');
|
||||
const createNativeBulkInsertStream = require('./createNativeBulkInsertStream');
|
||||
const AsyncLock = require('async-lock');
|
||||
const nativeDriver = require('./nativeDriver');
|
||||
const lock = new AsyncLock();
|
||||
const { tediousConnect, tediousQueryCore, tediousReadQuery, tediousStream } = require('./tediousDriver');
|
||||
const { nativeConnect, nativeQueryCore, nativeReadQuery, nativeStream } = nativeDriver;
|
||||
let msnodesqlv8;
|
||||
|
||||
const windowsAuthTypes = [
|
||||
{
|
||||
title: 'Windows',
|
||||
name: 'sspi',
|
||||
disabledFields: ['password', 'port', 'user'],
|
||||
},
|
||||
{
|
||||
title: 'SQL Server',
|
||||
name: 'sql',
|
||||
disabledFields: ['port'],
|
||||
},
|
||||
{
|
||||
title: 'Tedious driver',
|
||||
name: 'tedious',
|
||||
},
|
||||
];
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
analyserClass: MsSqlAnalyser,
|
||||
|
||||
getAuthTypes() {
|
||||
return msnodesqlv8 ? windowsAuthTypes : null;
|
||||
},
|
||||
|
||||
async connect(conn) {
|
||||
const { authType } = conn;
|
||||
if (msnodesqlv8 && (authType == 'sspi' || authType == 'sql')) {
|
||||
return nativeConnect(conn);
|
||||
}
|
||||
|
||||
return tediousConnect(conn);
|
||||
},
|
||||
async queryCore(pool, sql, options) {
|
||||
if (pool._connectionType == 'msnodesqlv8') {
|
||||
return nativeQueryCore(pool, sql, options);
|
||||
} else {
|
||||
return tediousQueryCore(pool, sql, options);
|
||||
}
|
||||
},
|
||||
async query(pool, sql, options) {
|
||||
return lock.acquire('connection', async () => {
|
||||
return this.queryCore(pool, sql, options);
|
||||
});
|
||||
},
|
||||
async stream(pool, sql, options) {
|
||||
if (pool._connectionType == 'msnodesqlv8') {
|
||||
return nativeStream(pool, sql, options);
|
||||
} else {
|
||||
return tediousStream(pool, sql, options);
|
||||
}
|
||||
},
|
||||
async readQuery(pool, sql, structure) {
|
||||
if (pool._connectionType == 'msnodesqlv8') {
|
||||
return nativeReadQuery(pool, sql, structure);
|
||||
} else {
|
||||
return tediousReadQuery(pool, sql, structure);
|
||||
}
|
||||
},
|
||||
async writeTable(pool, name, options) {
|
||||
if (pool._connectionType == 'msnodesqlv8') {
|
||||
return createNativeBulkInsertStream(this, stream, pool, name, options);
|
||||
} else {
|
||||
return createTediousBulkInsertStream(this, 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;
|
||||
},
|
||||
};
|
||||
|
||||
driver.initialize = dbgateEnv => {
|
||||
if (dbgateEnv.nativeModules && dbgateEnv.nativeModules.msnodesqlv8) {
|
||||
msnodesqlv8 = dbgateEnv.nativeModules.msnodesqlv8();
|
||||
}
|
||||
nativeDriver.initialize(dbgateEnv);
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
41
plugins/dbgate-plugin-mssql/src/backend/getConcreteType.js
Normal file
41
plugins/dbgate-plugin-mssql/src/backend/getConcreteType.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const tedious = require('tedious');
|
||||
|
||||
const { TYPES } = tedious;
|
||||
|
||||
const N_TYPES = {
|
||||
BitN: 0x68,
|
||||
DateTimeN: 0x6f,
|
||||
DecimalN: 0x6a,
|
||||
FloatN: 0x6d,
|
||||
IntN: 0x26,
|
||||
MoneyN: 0x6e,
|
||||
NumericN: 0x6c,
|
||||
};
|
||||
|
||||
function getConcreteType(type, length) {
|
||||
switch (type.id) {
|
||||
case N_TYPES.BitN:
|
||||
return TYPES.Bit;
|
||||
case N_TYPES.NumericN:
|
||||
return TYPES.Numeric;
|
||||
case N_TYPES.DecimalN:
|
||||
return TYPES.Decimal;
|
||||
case N_TYPES.IntN:
|
||||
if (length === 8) return TYPES.BigInt;
|
||||
if (length === 4) return TYPES.Int;
|
||||
if (length === 2) return TYPES.SmallInt;
|
||||
return TYPES.TinyInt;
|
||||
case N_TYPES.FloatN:
|
||||
if (length === 8) return TYPES.Float;
|
||||
return TYPES.Real;
|
||||
case N_TYPES.MoneyN:
|
||||
if (length === 8) return TYPES.Money;
|
||||
return TYPES.SmallMoney;
|
||||
case N_TYPES.DateTimeN:
|
||||
if (length === 8) return TYPES.DateTime;
|
||||
return TYPES.SmallDateTime;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
module.exports = getConcreteType;
|
||||
9
plugins/dbgate-plugin-mssql/src/backend/index.js
Normal file
9
plugins/dbgate-plugin-mssql/src/backend/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const driver = require('./driver');
|
||||
|
||||
module.exports = {
|
||||
packageName: 'dbgate-plugin-mssql',
|
||||
driver,
|
||||
initialize(dbgateEnv) {
|
||||
driver.initialize(dbgateEnv);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
function makeUniqueColumnNames(res) {
|
||||
const usedNames = new Set();
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
if (usedNames.has(res[i].columnName)) {
|
||||
let suffix = 2;
|
||||
while (usedNames.has(`${res[i].columnName}${suffix}`)) suffix++;
|
||||
res[i].columnName = `${res[i].columnName}${suffix}`;
|
||||
}
|
||||
usedNames.add(res[i].columnName);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = makeUniqueColumnNames;
|
||||
211
plugins/dbgate-plugin-mssql/src/backend/nativeDriver.js
Normal file
211
plugins/dbgate-plugin-mssql/src/backend/nativeDriver.js
Normal file
@@ -0,0 +1,211 @@
|
||||
const _ = require('lodash');
|
||||
const stream = require('stream');
|
||||
const makeUniqueColumnNames = require('./makeUniqueColumnNames');
|
||||
let msnodesqlv8;
|
||||
|
||||
// async function nativeQueryCore(pool, sql, options) {
|
||||
// if (sql == null) {
|
||||
// return Promise.resolve({
|
||||
// rows: [],
|
||||
// columns: [],
|
||||
// });
|
||||
// }
|
||||
// return new Promise((resolve, reject) => {
|
||||
// pool.query(sql, (err, rows) => {
|
||||
// if (err) reject(err);
|
||||
// resolve({
|
||||
// rows,
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
function extractNativeColumns(meta) {
|
||||
const res = meta.map(col => {
|
||||
const resCol = {
|
||||
columnName: col.name,
|
||||
dataType: col.sqlType.toLowerCase(),
|
||||
notNull: !col.nullable,
|
||||
};
|
||||
|
||||
if (resCol.dataType.endsWith(' identity')) {
|
||||
resCol.dataType = resCol.dataType.replace(' identity', '');
|
||||
resCol.autoIncrement = true;
|
||||
}
|
||||
if (col.size && resCol.dataType.includes('char')) {
|
||||
resCol.dataType += `(${col.size})`;
|
||||
}
|
||||
return resCol;
|
||||
});
|
||||
|
||||
makeUniqueColumnNames(res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async function nativeConnect({ server, port, user, password, database, authType }) {
|
||||
let connectionString = `server=${server}`;
|
||||
if (port && !server.includes('\\')) connectionString += `,${port}`;
|
||||
connectionString += ';Driver={SQL Server Native Client 11.0}';
|
||||
if (authType == 'sspi') connectionString += ';Trusted_Connection=Yes';
|
||||
else connectionString += `;UID=${user};PWD=${password}`;
|
||||
if (database) connectionString += `;Database=${database}`;
|
||||
return new Promise((resolve, reject) => {
|
||||
msnodesqlv8.open(connectionString, (err, conn) => {
|
||||
if (err) reject(err);
|
||||
conn._connectionType = 'msnodesqlv8';
|
||||
resolve(conn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function nativeQueryCore(pool, sql, options) {
|
||||
if (sql == null) {
|
||||
return Promise.resolve({
|
||||
rows: [],
|
||||
columns: [],
|
||||
});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let columns = null;
|
||||
let currentRow = null;
|
||||
const q = pool.query(sql);
|
||||
const rows = [];
|
||||
|
||||
q.on('meta', meta => {
|
||||
columns = extractNativeColumns(meta);
|
||||
});
|
||||
|
||||
q.on('column', (index, data) => {
|
||||
currentRow[columns[index].columnName] = data;
|
||||
});
|
||||
|
||||
q.on('row', index => {
|
||||
if (currentRow) rows.push(currentRow);
|
||||
currentRow = {};
|
||||
});
|
||||
|
||||
q.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
q.on('done', () => {
|
||||
if (currentRow) rows.push(currentRow);
|
||||
resolve({
|
||||
columns,
|
||||
rows,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function nativeReadQuery(pool, sql, structure) {
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
});
|
||||
|
||||
let columns = null;
|
||||
let currentRow = null;
|
||||
const q = pool.query(sql);
|
||||
|
||||
q.on('meta', meta => {
|
||||
columns = extractNativeColumns(meta);
|
||||
pass.write({
|
||||
__isStreamHeader: true,
|
||||
...(structure || { columns }),
|
||||
});
|
||||
});
|
||||
|
||||
q.on('column', (index, data) => {
|
||||
currentRow[columns[index].columnName] = data;
|
||||
});
|
||||
|
||||
q.on('row', index => {
|
||||
if (currentRow) pass.write(currentRow);
|
||||
currentRow = {};
|
||||
});
|
||||
|
||||
q.on('error', err => {
|
||||
console.error(err);
|
||||
pass.end();
|
||||
});
|
||||
|
||||
q.on('done', () => {
|
||||
if (currentRow) pass.write(currentRow);
|
||||
pass.end();
|
||||
});
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
async function nativeStream(pool, sql, options) {
|
||||
const handleInfo = info => {
|
||||
const { message, lineNumber, procName } = info;
|
||||
options.info({
|
||||
message,
|
||||
line: lineNumber,
|
||||
procedure: procName,
|
||||
time: new Date(),
|
||||
severity: 'info',
|
||||
});
|
||||
};
|
||||
const handleError = error => {
|
||||
const { message, lineNumber, procName } = error;
|
||||
options.info({
|
||||
message,
|
||||
line: lineNumber,
|
||||
procedure: procName,
|
||||
time: new Date(),
|
||||
severity: 'error',
|
||||
});
|
||||
};
|
||||
|
||||
let columns = null;
|
||||
let currentRow = null;
|
||||
const q = pool.query(sql);
|
||||
|
||||
q.on('meta', meta => {
|
||||
if (currentRow) options.row(currentRow);
|
||||
currentRow = null;
|
||||
columns = extractNativeColumns(meta);
|
||||
options.recordset(columns);
|
||||
});
|
||||
|
||||
q.on('column', (index, data) => {
|
||||
currentRow[columns[index].columnName] = data;
|
||||
});
|
||||
|
||||
q.on('row', index => {
|
||||
if (currentRow) options.row(currentRow);
|
||||
currentRow = {};
|
||||
});
|
||||
|
||||
q.on('error', err => {
|
||||
handleError(err);
|
||||
options.done();
|
||||
});
|
||||
|
||||
q.on('info', info => {
|
||||
handleInfo(info);
|
||||
});
|
||||
|
||||
q.on('done', () => {
|
||||
if (currentRow) options.row(currentRow);
|
||||
options.done();
|
||||
});
|
||||
}
|
||||
|
||||
const initialize = dbgateEnv => {
|
||||
if (dbgateEnv.nativeModules && dbgateEnv.nativeModules.msnodesqlv8) {
|
||||
msnodesqlv8 = dbgateEnv.nativeModules.msnodesqlv8();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
nativeConnect,
|
||||
nativeQueryCore,
|
||||
nativeReadQuery,
|
||||
nativeStream,
|
||||
initialize,
|
||||
};
|
||||
20
plugins/dbgate-plugin-mssql/src/backend/sql/columns.js
Normal file
20
plugins/dbgate-plugin-mssql/src/backend/sql/columns.js
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
`;
|
||||
40
plugins/dbgate-plugin-mssql/src/backend/sql/foreignKeys.js
Normal file
40
plugins/dbgate-plugin-mssql/src/backend/sql/foreignKeys.js
Normal file
@@ -0,0 +1,40 @@
|
||||
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]
|
||||
`;
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = `select schema_id as objectId, name as schemaName from sys.schemas`;
|
||||
23
plugins/dbgate-plugin-mssql/src/backend/sql/index.js
Normal file
23
plugins/dbgate-plugin-mssql/src/backend/sql/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
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
|
||||
`;
|
||||
@@ -0,0 +1,6 @@
|
||||
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
|
||||
`;
|
||||
14
plugins/dbgate-plugin-mssql/src/backend/sql/primaryKeys.js
Normal file
14
plugins/dbgate-plugin-mssql/src/backend/sql/primaryKeys.js
Normal file
@@ -0,0 +1,14 @@
|
||||
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]
|
||||
`;
|
||||
@@ -0,0 +1,6 @@
|
||||
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]
|
||||
`;
|
||||
8
plugins/dbgate-plugin-mssql/src/backend/sql/tables.js
Normal file
8
plugins/dbgate-plugin-mssql/src/backend/sql/tables.js
Normal file
@@ -0,0 +1,8 @@
|
||||
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]
|
||||
`;
|
||||
18
plugins/dbgate-plugin-mssql/src/backend/sql/viewColumns.js
Normal file
18
plugins/dbgate-plugin-mssql/src/backend/sql/viewColumns.js
Normal file
@@ -0,0 +1,18 @@
|
||||
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
|
||||
`;
|
||||
10
plugins/dbgate-plugin-mssql/src/backend/sql/views.js
Normal file
10
plugins/dbgate-plugin-mssql/src/backend/sql/views.js
Normal file
@@ -0,0 +1,10 @@
|
||||
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]
|
||||
`;
|
||||
180
plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js
Normal file
180
plugins/dbgate-plugin-mssql/src/backend/tediousDriver.js
Normal file
@@ -0,0 +1,180 @@
|
||||
const _ = require('lodash');
|
||||
const stream = require('stream');
|
||||
const tedious = require('tedious');
|
||||
const makeUniqueColumnNames = require('./makeUniqueColumnNames');
|
||||
|
||||
function extractTediousColumns(columns, addDriverNativeColumn = false) {
|
||||
const res = columns.map(col => {
|
||||
const resCol = {
|
||||
columnName: col.colName,
|
||||
dataType: col.type.name.toLowerCase(),
|
||||
driverNativeColumn: addDriverNativeColumn ? col : undefined,
|
||||
|
||||
notNull: !(col.flags & 0x01),
|
||||
autoIncrement: !!(col.flags & 0x10),
|
||||
};
|
||||
if (col.dataLength) resCol.dataType += `(${col.dataLength})`;
|
||||
return resCol;
|
||||
});
|
||||
|
||||
makeUniqueColumnNames(res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async function tediousConnect({ server, port, user, password, database, ssl }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const connection = new tedious.Connection({
|
||||
server,
|
||||
|
||||
authentication: {
|
||||
type: 'default',
|
||||
options: {
|
||||
userName: user,
|
||||
password: password,
|
||||
},
|
||||
},
|
||||
|
||||
options: {
|
||||
encrypt: !!ssl,
|
||||
cryptoCredentialsDetails: ssl ? _.pick(ssl, ['ca', 'cert', 'key']) : undefined,
|
||||
trustServerCertificate: ssl ? (!ssl.ca && !ssl.cert && !ssl.key ? true : ssl.rejectUnauthorized) : undefined,
|
||||
enableArithAbort: true,
|
||||
validateBulkLoadParameters: false,
|
||||
requestTimeout: 1000 * 3600,
|
||||
database,
|
||||
port: port ? parseInt(port) : undefined,
|
||||
},
|
||||
});
|
||||
connection.on('connect', function (err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
connection._connectionType = 'tedious';
|
||||
resolve(connection);
|
||||
});
|
||||
connection.connect();
|
||||
});
|
||||
}
|
||||
|
||||
async function tediousQueryCore(pool, sql, options) {
|
||||
if (sql == null) {
|
||||
return Promise.resolve({
|
||||
rows: [],
|
||||
columns: [],
|
||||
});
|
||||
}
|
||||
const { addDriverNativeColumn } = options || {};
|
||||
return new Promise((resolve, reject) => {
|
||||
const result = {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
const request = new tedious.Request(sql, (err, rowCount) => {
|
||||
if (err) reject(err);
|
||||
else resolve(result);
|
||||
});
|
||||
request.on('columnMetadata', function (columns) {
|
||||
result.columns = extractTediousColumns(columns, addDriverNativeColumn);
|
||||
});
|
||||
request.on('row', function (columns) {
|
||||
result.rows.push(
|
||||
_.zipObject(
|
||||
result.columns.map(x => x.columnName),
|
||||
columns.map(x => x.value)
|
||||
)
|
||||
);
|
||||
});
|
||||
pool.execSql(request);
|
||||
});
|
||||
}
|
||||
|
||||
async function tediousReadQuery(pool, sql, structure) {
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
});
|
||||
let currentColumns = [];
|
||||
|
||||
const request = new tedious.Request(sql, (err, rowCount) => {
|
||||
if (err) console.error(err);
|
||||
pass.end();
|
||||
});
|
||||
request.on('columnMetadata', function (columns) {
|
||||
currentColumns = extractTediousColumns(columns);
|
||||
pass.write({
|
||||
__isStreamHeader: true,
|
||||
...(structure || { columns: currentColumns }),
|
||||
});
|
||||
});
|
||||
request.on('row', function (columns) {
|
||||
const row = _.zipObject(
|
||||
currentColumns.map(x => x.columnName),
|
||||
columns.map(x => x.value)
|
||||
);
|
||||
pass.write(row);
|
||||
});
|
||||
pool.execSql(request);
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
async function tediousStream(pool, sql, options) {
|
||||
let currentColumns = [];
|
||||
|
||||
const handleInfo = info => {
|
||||
const { message, lineNumber, procName } = info;
|
||||
options.info({
|
||||
message,
|
||||
line: lineNumber,
|
||||
procedure: procName,
|
||||
time: new Date(),
|
||||
severity: 'info',
|
||||
});
|
||||
};
|
||||
const handleError = error => {
|
||||
const { message, lineNumber, procName } = error;
|
||||
options.info({
|
||||
message,
|
||||
line: lineNumber,
|
||||
procedure: procName,
|
||||
time: new Date(),
|
||||
severity: 'error',
|
||||
});
|
||||
};
|
||||
|
||||
pool.on('infoMessage', handleInfo);
|
||||
pool.on('errorMessage', handleError);
|
||||
const request = new tedious.Request(sql, (err, rowCount) => {
|
||||
// if (err) reject(err);
|
||||
// else resolve(result);
|
||||
options.done();
|
||||
pool.off('infoMessage', handleInfo);
|
||||
pool.off('errorMessage', handleError);
|
||||
|
||||
options.info({
|
||||
message: `${rowCount} rows affected`,
|
||||
time: new Date(),
|
||||
severity: 'info',
|
||||
});
|
||||
});
|
||||
request.on('columnMetadata', function (columns) {
|
||||
currentColumns = extractTediousColumns(columns);
|
||||
options.recordset(currentColumns);
|
||||
});
|
||||
request.on('row', function (columns) {
|
||||
const row = _.zipObject(
|
||||
currentColumns.map(x => x.columnName),
|
||||
columns.map(x => x.value)
|
||||
);
|
||||
options.row(row);
|
||||
});
|
||||
pool.execSqlBatch(request);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
tediousConnect,
|
||||
tediousQueryCore,
|
||||
tediousReadQuery,
|
||||
tediousStream,
|
||||
};
|
||||
113
plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js
Normal file
113
plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js
Normal file
@@ -0,0 +1,113 @@
|
||||
const { SqlDumper } = require('dbgate-tools');
|
||||
|
||||
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', 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)) + '-' + right('0' + ^convert(^varchar(100), ^datepart(^month, %c)), 2)",
|
||||
dumpExpr,
|
||||
dumpExpr
|
||||
);
|
||||
break;
|
||||
case 'GROUP:DAY':
|
||||
this.put(
|
||||
"^^convert(^varchar(100), ^datepart(^year, %c)) + '-' + ^right('0' + ^convert(^varchar(100), ^datepart(^month, %c)), 2)+'-' + ^right('0' + ^convert(^varchar(100), ^datepart(^day, %c)), 2)",
|
||||
dumpExpr,
|
||||
dumpExpr,
|
||||
dumpExpr
|
||||
);
|
||||
break;
|
||||
default:
|
||||
dumpExpr();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
renameObject(obj, newname) {
|
||||
this.putCmd("^execute sp_rename '%f', '%s', 'OBJECT'", obj, newname);
|
||||
}
|
||||
|
||||
changeObjectSchema(obj, newschema) {
|
||||
this.putCmd("^execute sp_changeobjectowner '%f', '%s'", obj, newschema);
|
||||
}
|
||||
|
||||
dropTable(obj, options = {}) {
|
||||
if (options.testIfExists) {
|
||||
this.put("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'%f') AND type in (N'U'))&n", obj);
|
||||
}
|
||||
super.dropTable(obj, options);
|
||||
}
|
||||
|
||||
dropDefault(col) {
|
||||
if (col.defaultConstraint) {
|
||||
this.putCmd("^alter ^table %f ^drop ^constraint %i", col, col.defaultConstraint);
|
||||
}
|
||||
}
|
||||
|
||||
guessDefaultName(col) {
|
||||
return col.defaultConstraint || `DF${col.schemaName || 'dbo'}_${col.pureName}_col.columnName`
|
||||
}
|
||||
|
||||
createDefault(col) {
|
||||
if (!col.defaultValue) return;
|
||||
const defsql = col.defaultValue;
|
||||
if (!defsql) {
|
||||
const defname = this.guessDefaultName(col);
|
||||
this.putCmd("^alter ^table %f ^add ^constraint %i ^default %s for %i", col, defname, defsql, col.columnName);
|
||||
}
|
||||
}
|
||||
|
||||
renameColumn(column, newcol) {
|
||||
this.putCmd("^execute sp_rename '%f.%i', '%s', 'COLUMN'", column, column.columnName, newcol);
|
||||
}
|
||||
|
||||
renameConstraint(cnt, newname) {
|
||||
if (cnt.constraintType == 'index') this.putCmd("^execute sp_rename '%f.%i', '%s', 'INDEX'", cnt, cnt.constraintName, newname);
|
||||
else this.putCmd("^execute sp_rename '%f', '%s', 'OBJECT'", { schemaName: cnt.schemaName, pureName: cnt.constraintName }, newname);
|
||||
}
|
||||
}
|
||||
|
||||
MsSqlDumper.prototype.renameView = MsSqlDumper.prototype.renameObject;
|
||||
MsSqlDumper.prototype.changeViewSchema = MsSqlDumper.prototype.changeObjectSchema;
|
||||
|
||||
MsSqlDumper.prototype.renameProcedure = MsSqlDumper.prototype.renameObject;
|
||||
MsSqlDumper.prototype.changeProcedureSchema = MsSqlDumper.prototype.changeObjectSchema;
|
||||
|
||||
MsSqlDumper.prototype.renameFunction = MsSqlDumper.prototype.renameObject;
|
||||
MsSqlDumper.prototype.changeFunctionSchema = MsSqlDumper.prototype.changeObjectSchema;
|
||||
|
||||
MsSqlDumper.prototype.renameTrigger = MsSqlDumper.prototype.renameObject;
|
||||
MsSqlDumper.prototype.changeTriggerSchema = MsSqlDumper.prototype.changeObjectSchema;
|
||||
|
||||
MsSqlDumper.prototype.renameTable = MsSqlDumper.prototype.renameObject;
|
||||
MsSqlDumper.prototype.changeTableSchema = MsSqlDumper.prototype.changeObjectSchema;
|
||||
|
||||
|
||||
module.exports = MsSqlDumper;
|
||||
29
plugins/dbgate-plugin-mssql/src/frontend/driver.js
Normal file
29
plugins/dbgate-plugin-mssql/src/frontend/driver.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const { driverBase } = require('dbgate-tools');
|
||||
const MsSqlDumper = require('./MsSqlDumper');
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
const dialect = {
|
||||
limitSelect: true,
|
||||
rangeSelect: true,
|
||||
offsetFetchRangeSyntax: true,
|
||||
stringEscapeChar: "'",
|
||||
fallbackDataType: 'nvarchar(max)',
|
||||
explicitDropConstraint: false,
|
||||
enableConstraintsPerTable: true,
|
||||
anonymousPrimaryKey: false,
|
||||
quoteIdentifier(s) {
|
||||
return `[${s}]`;
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
dumperClass: MsSqlDumper,
|
||||
dialect,
|
||||
engine: 'mssql@dbgate-plugin-mssql',
|
||||
title: 'Microsoft SQL Server',
|
||||
defaultPort: 1433,
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
5
plugins/dbgate-plugin-mssql/src/frontend/index.js
Normal file
5
plugins/dbgate-plugin-mssql/src/frontend/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import driver from './driver';
|
||||
|
||||
export default {
|
||||
driver,
|
||||
};
|
||||
22
plugins/dbgate-plugin-mssql/webpack-backend.config.js
Normal file
22
plugins/dbgate-plugin-mssql/webpack-backend.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
|
||||
var config = {
|
||||
context: __dirname + '/src/backend',
|
||||
|
||||
entry: {
|
||||
app: './index.js',
|
||||
},
|
||||
target: 'node',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'backend.js',
|
||||
libraryTarget: 'commonjs2',
|
||||
},
|
||||
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
23
plugins/dbgate-plugin-mssql/webpack-frontend.config.js
Normal file
23
plugins/dbgate-plugin-mssql/webpack-frontend.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var webpack = require("webpack");
|
||||
var path = require("path");
|
||||
|
||||
var config = {
|
||||
context: __dirname + "/src/frontend",
|
||||
|
||||
entry: {
|
||||
app: "./index.js",
|
||||
},
|
||||
target: "web",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "frontend.js",
|
||||
libraryTarget: "var",
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
21
plugins/dbgate-plugin-mysql/LICENSE
Normal file
21
plugins/dbgate-plugin-mysql/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Jan Prochazka
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
6
plugins/dbgate-plugin-mysql/README.md
Normal file
6
plugins/dbgate-plugin-mysql/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://www.npmjs.com/package/dbgate-plugin-mysql)
|
||||
|
||||
# dbgate-plugin-mysql
|
||||
|
||||
Use DbGate for install of this plugin
|
||||
47
plugins/dbgate-plugin-mysql/icon.svg
Normal file
47
plugins/dbgate-plugin-mysql/icon.svg
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path style="fill:#F2F2F2;" d="M512,256c0,36.937-7.826,72.056-21.911,103.769c-2.288,5.183-4.765,10.271-7.387,15.266
|
||||
C439.85,456.474,354.408,512,256,512c-103.685,0-192.972-61.639-233.221-150.277c-7.022-15.464-12.549-31.744-16.394-48.661
|
||||
C2.205,294.713,0,275.613,0,256C0,114.615,114.615,0,256,0c115.012,0,212.302,75.839,244.611,180.245
|
||||
c1.024,3.323,1.985,6.687,2.884,10.073v0.01c1.672,6.332,3.114,12.737,4.305,19.247c0.554,3.03,1.055,6.071,1.494,9.132
|
||||
c0.303,2.048,0.585,4.096,0.825,6.165c0.46,3.699,0.825,7.429,1.118,11.18C511.739,242.636,512,249.292,512,256z"/>
|
||||
<g>
|
||||
<path style="fill:#5181A2;" d="M367.961,77.614c0,0,7.42,7.632,8.691,13.355c0,0,5.3-4.028,1.696-10.387
|
||||
C374.744,74.222,367.961,77.614,367.961,77.614z"/>
|
||||
<path style="fill:#5181A2;" d="M503.495,190.318c-6.666-3.354-12.727-5.339-17.471-6.51c-6.416-1.588-11.609-6.311-13.855-12.528
|
||||
c-2.884-7.973-8.255-20.888-18.139-40.458c-19.508-38.578-41.127-50.448-55.327-60.416c-9.32-6.541-21.567-7.868-28.881-8.014
|
||||
c-3.688-0.073-7.241-1.421-10.073-3.793c-7.45-6.269-22.1-13.636-26.76-10.877c-5.726,3.396,0,11.243,8.046,21.201
|
||||
c8.056,9.968,9.759,20.992,15.475,35.83c2.644,6.844,5.831,10.982,8.485,13.448c2.779,2.591,3.688,6.614,2.215,10.104
|
||||
c-5.371,12.696-7.168,28.776-4.127,43.645c3.605,17.596,11.87,17.387,11.87,17.387c-0.846-23.531,11.452-33.708,11.452-33.708
|
||||
c11.233,34.335,36.885,59.988,36.885,59.988c-20.773-7.419-36.247-41.336-36.247-41.336s-1.693,1.693-3.177,12.936
|
||||
c-1.484,11.233-15.057,18.014-25.015-6.572c-9.968-24.597,2.967-55.547,2.967-55.547c-5.726-2.968-17.596-30.312-17.596-34.555
|
||||
c0-4.232-4.671-13.563-10.386-19.289c-5.726-5.716-16.959-22.256-3.814-29.884c13.134-7.638,39.215,12.925,39.215,12.925
|
||||
c41.127-1.484,83.519,48.546,90.3,66.351c6.781,17.805,25.861,53.426,25.861,53.426c5.413,1.641,10.501,3.762,15.214,6.175
|
||||
C501.635,183.568,502.596,186.932,503.495,190.318z"/>
|
||||
<path style="fill:#5181A2;" d="M511.237,236.053c-0.209-0.094-0.408-0.199-0.606-0.293c-13.145-6.363-18.442-19.717-18.442-19.717
|
||||
c3.406-3.406,9.446-5.36,15.611-6.468c0.554,3.03,1.055,6.071,1.494,9.132c-2.299,0.449-3.751,0.721-3.751,0.721
|
||||
c0,1.296,1.808,3.229,4.577,5.444C510.579,228.571,510.945,232.302,511.237,236.053z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFA600;" d="M133.371,374.659H113.34c0,0,0-45.777-6.677-100.153L75.18,374.659H58.964l-31.953-99.673
|
||||
c0,0-3.72,29.288-4.232,86.737c-7.022-15.464-12.549-31.744-16.394-48.661c1.473-21.264,3.542-44.44,6.322-63.352h26.07
|
||||
l29.727,92.526l31.483-92.526h23.845C123.831,249.71,133.371,331.264,133.371,374.659z"/>
|
||||
<path style="fill:#FFA600;" d="M144.423,283.452l20.668-0.318c0,0,23.212,69.637,20.987,78.222c0,0,14.945-41.655,18.761-78.222
|
||||
h20.35c0,0-10.812,73.771-43.88,112.564c-12.375,14.517-26.262,14.695-38.799,9.236v-11.143c0,0,17.177,6.042,26.081-6.678
|
||||
c7.168-10.24,0.322-27.898-5.938-46.671C161.135,335.896,144.423,283.452,144.423,283.452z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#5181A2;" d="M237.908,365.491l5.405-10.812c0,0,37.204,16.534,46.742,0c9.539-16.534,0.318-25.438-18.442-35.296
|
||||
c-18.76-9.858-38.475-22.258-29.89-49.287c8.586-27.028,54.056-25.12,67.093-12.083l-4.451,10.493c0,0-33.705-14.309-42.926,4.451
|
||||
c-9.221,18.76,15.263,27.664,20.987,30.208c5.724,2.544,37.521,15.58,34.024,38.793s-17.807,37.521-50.558,34.659
|
||||
C254.124,373.758,247.447,371.85,237.908,365.491z"/>
|
||||
<path style="fill:#5181A2;" d="M405.452,367c14.054-11.295,20.083-31.389,20.083-54.293c0-35.328-10.167-63.969-50.333-63.969
|
||||
c-34.586,0-50.333,28.641-50.333,63.969s11.515,63.969,50.333,63.969c5.622,0,10.721-0.7,15.318-2.017l29.905,17.544l7.314-14.315
|
||||
L405.452,367z M375.202,361.357c-22.507,0-29.184-21.786-29.184-48.65c0-26.875,6.363-48.65,29.184-48.65
|
||||
c23.28,0,29.174,21.776,29.174,48.65C404.375,339.571,398.482,361.357,375.202,361.357z"/>
|
||||
<path style="fill:#5181A2;" d="M490.088,359.769c-2.288,5.183-4.765,10.271-7.387,15.266h-39.382V250.378h19.717v109.39H490.088z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
39
plugins/dbgate-plugin-mysql/package.json
Normal file
39
plugins/dbgate-plugin-mysql/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "dbgate-plugin-mysql",
|
||||
"main": "dist/backend.js",
|
||||
"version": "1.2.2",
|
||||
"homepage": "https://github.com/dbgate/dbgate-plugin-mysql",
|
||||
"description": "MySQL connect plugin for DbGate",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate-plugin-mysql.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"sql",
|
||||
"dbgate",
|
||||
"dbgateplugin",
|
||||
"mysql"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build:frontend": "webpack --config webpack-frontend.config",
|
||||
"build:backend": "webpack --config webpack-backend.config.js",
|
||||
"build": "yarn build:frontend && yarn build:backend",
|
||||
"plugin": "yarn build && yarn pack && dbgate-plugin dbgate-plugin-mysql",
|
||||
"plugout": "dbgate-plugout dbgate-plugin-mysql",
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verycrazydog/mysql-parser": "^1.2.0",
|
||||
"dbgate-plugin-tools": "^1.0.4",
|
||||
"dbgate-tools": "^4.0.3-rc.1",
|
||||
"mysql2": "^2.2.5",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
}
|
||||
9
plugins/dbgate-plugin-mysql/prettier.config.js
Normal file
9
plugins/dbgate-plugin-mysql/prettier.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
arrowParen: 'avoid',
|
||||
arrowParens: 'avoid',
|
||||
printWidth: 120,
|
||||
};
|
||||
222
plugins/dbgate-plugin-mysql/src/backend/Analyser.js
Normal file
222
plugins/dbgate-plugin-mysql/src/backend/Analyser.js
Normal file
@@ -0,0 +1,222 @@
|
||||
const fp = require('lodash/fp');
|
||||
const _ = require('lodash');
|
||||
const sql = require('./sql');
|
||||
|
||||
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||
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 Analyser extends DatabaseAnalyser {
|
||||
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,
|
||||
objectId: table.pureName,
|
||||
columns: columns.rows.filter(col => col.pureName == table.pureName).map(getColumnInfo),
|
||||
primaryKey: DatabaseAnalyser.extractPrimaryKeys(table, pkColumns.rows),
|
||||
foreignKeys: DatabaseAnalyser.extractForeignKeys(table, fkColumns.rows),
|
||||
})),
|
||||
views: views.rows.map(view => ({
|
||||
...view,
|
||||
objectId: view.pureName,
|
||||
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']))
|
||||
.map(x => ({ ...x, objectId: x.pureName })),
|
||||
functions: programmables.rows
|
||||
.filter(x => x.objectType == 'FUNCTION')
|
||||
.map(fp.omit(['objectType']))
|
||||
.map(x => ({ ...x, objectId: x.pureName })),
|
||||
});
|
||||
}
|
||||
|
||||
getDeletedObjectsForField(nameArray, objectTypeField) {
|
||||
return this.structure[objectTypeField]
|
||||
.filter(x => !nameArray.includes(x.pureName))
|
||||
.map(x => ({
|
||||
oldName: _.pick(x, ['pureName']),
|
||||
action: 'remove',
|
||||
objectTypeField,
|
||||
objectId: x.pureName,
|
||||
}));
|
||||
}
|
||||
|
||||
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,
|
||||
objectId: pureName,
|
||||
}
|
||||
: {
|
||||
newName: { pureName },
|
||||
action: 'add',
|
||||
objectTypeField: field,
|
||||
objectId: pureName,
|
||||
};
|
||||
return action;
|
||||
});
|
||||
|
||||
return [..._.compact(modifications), ...this.getDeletedObjects([...allModifications.map(x => x.pureName)])];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Analyser;
|
||||
186
plugins/dbgate-plugin-mysql/src/backend/driver.js
Normal file
186
plugins/dbgate-plugin-mysql/src/backend/driver.js
Normal file
@@ -0,0 +1,186 @@
|
||||
const _ = require('lodash');
|
||||
const stream = require('stream');
|
||||
const driverBase = require('../frontend/driver');
|
||||
const Analyser = require('./Analyser');
|
||||
const mysql2 = require('mysql2');
|
||||
const { createBulkInsertStreamBase, makeUniqueColumnNames } = require('dbgate-tools');
|
||||
const mysqlSplitter = require('@verycrazydog/mysql-parser');
|
||||
|
||||
function extractColumns(fields) {
|
||||
if (fields) {
|
||||
const res = fields.map(col => ({
|
||||
columnName: col.name,
|
||||
}));
|
||||
makeUniqueColumnNames(res);
|
||||
return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function zipDataRow(rowArray, columns) {
|
||||
return _.zipObject(
|
||||
columns.map(x => x.columnName),
|
||||
rowArray
|
||||
);
|
||||
}
|
||||
|
||||
async function runQueryItem(connection, sql) {
|
||||
return new Promise((resolve, reject) => {
|
||||
connection.query(sql, function (error, results, fields) {
|
||||
if (error) reject(error);
|
||||
const columns = extractColumns(fields);
|
||||
resolve({ rows: results && columns && results.map && results.map(row => zipDataRow(row, columns)), columns });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function runStreamItem(connection, sql, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = connection.query(sql);
|
||||
let columns = [];
|
||||
|
||||
// const handleInfo = (info) => {
|
||||
// const { message, lineNumber, procName } = info;
|
||||
// options.info({
|
||||
// message,
|
||||
// line: lineNumber,
|
||||
// procedure: procName,
|
||||
// time: new Date(),
|
||||
// severity: 'info',
|
||||
// });
|
||||
// };
|
||||
|
||||
const handleEnd = () => {
|
||||
resolve();
|
||||
};
|
||||
|
||||
const handleRow = row => {
|
||||
if (row && row.constructor && row.constructor.name == 'OkPacket') {
|
||||
options.info({
|
||||
message: `${row.affectedRows} rows affected`,
|
||||
time: new Date(),
|
||||
severity: 'info',
|
||||
});
|
||||
} else {
|
||||
if (columns) {
|
||||
options.row(zipDataRow(row, columns));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleFields = fields => {
|
||||
columns = extractColumns(fields);
|
||||
if (columns) options.recordset(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);
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
analyserClass: Analyser,
|
||||
|
||||
async connect({ server, port, user, password, database, ssl }) {
|
||||
const connection = mysql2.createConnection({
|
||||
host: server,
|
||||
port,
|
||||
user,
|
||||
password,
|
||||
database,
|
||||
ssl,
|
||||
rowsAsArray: true,
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: true,
|
||||
// TODO: test following options
|
||||
// multipleStatements: true,
|
||||
// dateStrings: true,
|
||||
});
|
||||
connection._database_name = database;
|
||||
return connection;
|
||||
},
|
||||
async query(connection, sql) {
|
||||
if (sql == null) {
|
||||
return {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
}
|
||||
|
||||
const sqlSplitted = mysqlSplitter.split(sql);
|
||||
let res = {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
for (const sqlItem of sqlSplitted) {
|
||||
const resultItem = await runQueryItem(connection, sqlItem);
|
||||
if (resultItem.rows && resultItem.columns && resultItem.columns.length > 0) {
|
||||
res = resultItem;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
},
|
||||
async stream(connection, sql, options) {
|
||||
const sqlSplitted = mysqlSplitter.split(sql);
|
||||
|
||||
for (const sqlItem of sqlSplitted) {
|
||||
await runStreamItem(connection, sqlItem, options);
|
||||
}
|
||||
|
||||
options.done();
|
||||
},
|
||||
async readQuery(connection, sql, structure) {
|
||||
const query = connection.query(sql);
|
||||
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
});
|
||||
|
||||
let columns = [];
|
||||
query
|
||||
.on('error', err => {
|
||||
console.error(err);
|
||||
pass.end();
|
||||
})
|
||||
.on('fields', fields => {
|
||||
columns = extractColumns(fields);
|
||||
pass.write({
|
||||
__isStreamHeader: true,
|
||||
...(structure || { columns }),
|
||||
});
|
||||
})
|
||||
.on('result', row => pass.write(zipDataRow(row, columns)))
|
||||
.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 listDatabases(connection) {
|
||||
const { rows } = await this.query(connection, 'show databases');
|
||||
return rows.map(x => ({ name: x.Database }));
|
||||
},
|
||||
async writeTable(pool, name, options) {
|
||||
// @ts-ignore
|
||||
return createBulkInsertStreamBase(this, stream, pool, name, options);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
6
plugins/dbgate-plugin-mysql/src/backend/index.js
Normal file
6
plugins/dbgate-plugin-mysql/src/backend/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const driver = require('./driver');
|
||||
|
||||
module.exports = {
|
||||
packageName: 'dbgate-plugin-mysql',
|
||||
driver,
|
||||
};
|
||||
15
plugins/dbgate-plugin-mysql/src/backend/sql/columns.js
Normal file
15
plugins/dbgate-plugin-mysql/src/backend/sql/columns.js
Normal file
@@ -0,0 +1,15 @@
|
||||
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
|
||||
`;
|
||||
17
plugins/dbgate-plugin-mysql/src/backend/sql/foreignKeys.js
Normal file
17
plugins/dbgate-plugin-mysql/src/backend/sql/foreignKeys.js
Normal file
@@ -0,0 +1,17 @@
|
||||
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
|
||||
`;
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = `
|
||||
SHOW FUNCTION STATUS WHERE Db = '#DATABASE#'
|
||||
`;
|
||||
21
plugins/dbgate-plugin-mysql/src/backend/sql/index.js
Normal file
21
plugins/dbgate-plugin-mysql/src/backend/sql/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
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,
|
||||
};
|
||||
12
plugins/dbgate-plugin-mysql/src/backend/sql/primaryKeys.js
Normal file
12
plugins/dbgate-plugin-mysql/src/backend/sql/primaryKeys.js
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
||||
`;
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = `
|
||||
SHOW PROCEDURE STATUS WHERE Db = '#DATABASE#'
|
||||
`;
|
||||
@@ -0,0 +1,9 @@
|
||||
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]
|
||||
`;
|
||||
@@ -0,0 +1,8 @@
|
||||
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#'
|
||||
`;
|
||||
7
plugins/dbgate-plugin-mysql/src/backend/sql/tables.js
Normal file
7
plugins/dbgate-plugin-mysql/src/backend/sql/tables.js
Normal file
@@ -0,0 +1,7 @@
|
||||
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];
|
||||
`;
|
||||
7
plugins/dbgate-plugin-mysql/src/backend/sql/views.js
Normal file
7
plugins/dbgate-plugin-mysql/src/backend/sql/views.js
Normal file
@@ -0,0 +1,7 @@
|
||||
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';
|
||||
`;
|
||||
68
plugins/dbgate-plugin-mysql/src/frontend/Dumper.js
Normal file
68
plugins/dbgate-plugin-mysql/src/frontend/Dumper.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const { SqlDumper } = require('dbgate-tools');
|
||||
|
||||
class Dumper 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;
|
||||
}
|
||||
}
|
||||
|
||||
renameTable(obj, newName) {
|
||||
this.putCmd('^rename ^table %f ^to %i', obj, newName);
|
||||
}
|
||||
|
||||
changeColumn(oldcol, newcol, constraints) {
|
||||
this.put('^alter ^table %f ^change ^column %i %i ', oldcol, oldcol.columnName, newcol.columnName);
|
||||
this.columnDefinition(newcol, true, true, true);
|
||||
this.inlineConstraints(constraints);
|
||||
this.endCommand();
|
||||
}
|
||||
|
||||
renameColumn(column, newcol) {
|
||||
this.changeColumn(
|
||||
column,
|
||||
{
|
||||
...column,
|
||||
columnName: newcol,
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
enableConstraints(table, enabled) {
|
||||
this.putCmd('^set FOREIGN_KEY_CHECKS = %s', enabled ? '1' : '0');
|
||||
}
|
||||
|
||||
comment(value) {
|
||||
this.put('/* %s */', value);
|
||||
}
|
||||
|
||||
beginTransaction() {
|
||||
this.putCmd('^start ^transaction');
|
||||
}
|
||||
|
||||
selectTableIntoNewTable(sourceName, targetName) {
|
||||
this.putCmd('^create ^table %f (^select * ^from %f)', targetName, sourceName);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Dumper;
|
||||
27
plugins/dbgate-plugin-mysql/src/frontend/driver.js
Normal file
27
plugins/dbgate-plugin-mysql/src/frontend/driver.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { driverBase } = require('dbgate-tools');
|
||||
const Dumper = require('./Dumper');
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
const dialect = {
|
||||
rangeSelect: true,
|
||||
stringEscapeChar: '\\',
|
||||
fallbackDataType: 'longtext',
|
||||
enableConstraintsPerTable: false,
|
||||
anonymousPrimaryKey: true,
|
||||
explicitDropConstraint: true,
|
||||
quoteIdentifier(s) {
|
||||
return '`' + s + '`';
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
dumperClass: Dumper,
|
||||
dialect,
|
||||
engine: 'mysql@dbgate-plugin-mysql',
|
||||
title: 'MySQL / MariaDB',
|
||||
defaultPort: 3306,
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
6
plugins/dbgate-plugin-mysql/src/frontend/index.js
Normal file
6
plugins/dbgate-plugin-mysql/src/frontend/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import driver from './driver';
|
||||
|
||||
export default {
|
||||
packageName: 'dbgate-plugin-mysql',
|
||||
driver,
|
||||
};
|
||||
33
plugins/dbgate-plugin-mysql/test/testdb.sql
Normal file
33
plugins/dbgate-plugin-mysql/test/testdb.sql
Normal file
File diff suppressed because one or more lines are too long
23
plugins/dbgate-plugin-mysql/webpack-backend.config.js
Normal file
23
plugins/dbgate-plugin-mysql/webpack-backend.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
|
||||
var config = {
|
||||
context: __dirname + '/src/backend',
|
||||
|
||||
entry: {
|
||||
app: './index.js',
|
||||
},
|
||||
target: 'node',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'backend.js',
|
||||
libraryTarget: 'commonjs2',
|
||||
},
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
24
plugins/dbgate-plugin-mysql/webpack-frontend.config.js
Normal file
24
plugins/dbgate-plugin-mysql/webpack-frontend.config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
var webpack = require("webpack");
|
||||
var path = require("path");
|
||||
|
||||
var config = {
|
||||
context: __dirname + "/src/frontend",
|
||||
|
||||
entry: {
|
||||
app: "./index.js",
|
||||
},
|
||||
target: "web",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "frontend.js",
|
||||
libraryTarget: "var",
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
21
plugins/dbgate-plugin-postgres/LICENSE
Normal file
21
plugins/dbgate-plugin-postgres/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Jan Prochazka
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
6
plugins/dbgate-plugin-postgres/README.md
Normal file
6
plugins/dbgate-plugin-postgres/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://www.npmjs.com/package/dbgate-plugin-postgres)
|
||||
|
||||
# dbgate-plugin-postgres
|
||||
|
||||
Use DbGate for install of this plugin
|
||||
22
plugins/dbgate-plugin-postgres/icon.svg
Normal file
22
plugins/dbgate-plugin-postgres/icon.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="432.071pt" height="445.383pt" viewBox="0 0 432.071 445.383" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="orginal" style="fill-rule:nonzero;clip-rule:nonzero;stroke:#000000;stroke-miterlimit:4;">
|
||||
</g>
|
||||
<g id="Layer_x0020_3" style="fill-rule:nonzero;clip-rule:nonzero;fill:none;stroke:#FFFFFF;stroke-width:12.4651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;">
|
||||
<path style="fill:#000000;stroke:#000000;stroke-width:37.3953;stroke-linecap:butt;stroke-linejoin:miter;" d="M323.205,324.227c2.833-23.601,1.984-27.062,19.563-23.239l4.463,0.392c13.517,0.615,31.199-2.174,41.587-7c22.362-10.376,35.622-27.7,13.572-23.148c-50.297,10.376-53.755-6.655-53.755-6.655c53.111-78.803,75.313-178.836,56.149-203.322 C352.514-5.534,262.036,26.049,260.522,26.869l-0.482,0.089c-9.938-2.062-21.06-3.294-33.554-3.496c-22.761-0.374-40.032,5.967-53.133,15.904c0,0-161.408-66.498-153.899,83.628c1.597,31.936,45.777,241.655,98.47,178.31 c19.259-23.163,37.871-42.748,37.871-42.748c9.242,6.14,20.307,9.272,31.912,8.147l0.897-0.765c-0.281,2.876-0.157,5.689,0.359,9.019c-13.572,15.167-9.584,17.83-36.723,23.416c-27.457,5.659-11.326,15.734-0.797,18.367c12.768,3.193,42.305,7.716,62.268-20.224 l-0.795,3.188c5.325,4.26,4.965,30.619,5.72,49.452c0.756,18.834,2.017,36.409,5.856,46.771c3.839,10.36,8.369,37.05,44.036,29.406c29.809-6.388,52.6-15.582,54.677-101.107"/>
|
||||
<path style="fill:#336791;stroke:none;" d="M402.395,271.23c-50.302,10.376-53.76-6.655-53.76-6.655c53.111-78.808,75.313-178.843,56.153-203.326c-52.27-66.785-142.752-35.2-144.262-34.38l-0.486,0.087c-9.938-2.063-21.06-3.292-33.56-3.496c-22.761-0.373-40.026,5.967-53.127,15.902 c0,0-161.411-66.495-153.904,83.63c1.597,31.938,45.776,241.657,98.471,178.312c19.26-23.163,37.869-42.748,37.869-42.748c9.243,6.14,20.308,9.272,31.908,8.147l0.901-0.765c-0.28,2.876-0.152,5.689,0.361,9.019c-13.575,15.167-9.586,17.83-36.723,23.416 c-27.459,5.659-11.328,15.734-0.796,18.367c12.768,3.193,42.307,7.716,62.266-20.224l-0.796,3.188c5.319,4.26,9.054,27.711,8.428,48.969c-0.626,21.259-1.044,35.854,3.147,47.254c4.191,11.4,8.368,37.05,44.042,29.406c29.809-6.388,45.256-22.942,47.405-50.555 c1.525-19.631,4.976-16.729,5.194-34.28l2.768-8.309c3.192-26.611,0.507-35.196,18.872-31.203l4.463,0.392c13.517,0.615,31.208-2.174,41.591-7c22.358-10.376,35.618-27.7,13.573-23.148z"/>
|
||||
<path d="M215.866,286.484c-1.385,49.516,0.348,99.377,5.193,111.495c4.848,12.118,15.223,35.688,50.9,28.045c29.806-6.39,40.651-18.756,45.357-46.051c3.466-20.082,10.148-75.854,11.005-87.281"/>
|
||||
<path d="M173.104,38.256c0,0-161.521-66.016-154.012,84.109c1.597,31.938,45.779,241.664,98.473,178.316c19.256-23.166,36.671-41.335,36.671-41.335"/>
|
||||
<path d="M260.349,26.207c-5.591,1.753,89.848-34.889,144.087,34.417c19.159,24.484-3.043,124.519-56.153,203.329"/>
|
||||
<path style="stroke-linejoin:bevel;" d="M348.282,263.953c0,0,3.461,17.036,53.764,6.653c22.04-4.552,8.776,12.774-13.577,23.155c-18.345,8.514-59.474,10.696-60.146-1.069c-1.729-30.355,21.647-21.133,19.96-28.739c-1.525-6.85-11.979-13.573-18.894-30.338 c-6.037-14.633-82.796-126.849,21.287-110.183c3.813-0.789-27.146-99.002-124.553-100.599c-97.385-1.597-94.19,119.762-94.19,119.762"/>
|
||||
<path d="M188.604,274.334c-13.577,15.166-9.584,17.829-36.723,23.417c-27.459,5.66-11.326,15.733-0.797,18.365c12.768,3.195,42.307,7.718,62.266-20.229c6.078-8.509-0.036-22.086-8.385-25.547c-4.034-1.671-9.428-3.765-16.361,3.994z"/>
|
||||
<path d="M187.715,274.069c-1.368-8.917,2.93-19.528,7.536-31.942c6.922-18.626,22.893-37.255,10.117-96.339c-9.523-44.029-73.396-9.163-73.436-3.193c-0.039,5.968,2.889,30.26-1.067,58.548c-5.162,36.913,23.488,68.132,56.479,64.938"/>
|
||||
<path style="fill:#FFFFFF;stroke-width:4.155;stroke-linecap:butt;stroke-linejoin:miter;" d="M172.517,141.7c-0.288,2.039,3.733,7.48,8.976,8.207c5.234,0.73,9.714-3.522,9.998-5.559c0.284-2.039-3.732-4.285-8.977-5.015c-5.237-0.731-9.719,0.333-9.996,2.367z"/>
|
||||
<path style="fill:#FFFFFF;stroke-width:2.0775;stroke-linecap:butt;stroke-linejoin:miter;" d="M331.941,137.543c0.284,2.039-3.732,7.48-8.976,8.207c-5.238,0.73-9.718-3.522-10.005-5.559c-0.277-2.039,3.74-4.285,8.979-5.015c5.239-0.73,9.718,0.333,10.002,2.368z"/>
|
||||
<path d="M350.676,123.432c0.863,15.994-3.445,26.888-3.988,43.914c-0.804,24.748,11.799,53.074-7.191,81.435"/>
|
||||
<path style="stroke-width:3;" d="M0,60.232"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
39
plugins/dbgate-plugin-postgres/package.json
Normal file
39
plugins/dbgate-plugin-postgres/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "dbgate-plugin-postgres",
|
||||
"main": "dist/backend.js",
|
||||
"version": "1.2.2",
|
||||
"license": "MIT",
|
||||
"description": "PostgreSQL connector plugin for DbGate",
|
||||
"homepage": "https://github.com/dbgate/dbgate-plugin-postgres",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate-plugin-postgres.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"keywords": [
|
||||
"dbgate",
|
||||
"dbgateplugin",
|
||||
"postgresql"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build:frontend": "webpack --config webpack-frontend.config",
|
||||
"build:backend": "webpack --config webpack-backend.config.js",
|
||||
"build": "yarn build:frontend && yarn build:backend",
|
||||
"plugin": "yarn build && yarn pack && dbgate-plugin dbgate-plugin-postgres",
|
||||
"plugout": "dbgate-plugout dbgate-plugin-postgres",
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dbgate-plugin-tools": "^1.0.4",
|
||||
"dbgate-tools": "^4.0.3-rc.1",
|
||||
"lodash": "^4.17.15",
|
||||
"pg": "^7.17.0",
|
||||
"pg-query-stream": "^3.1.1",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
}
|
||||
9
plugins/dbgate-plugin-postgres/prettier.config.js
Normal file
9
plugins/dbgate-plugin-postgres/prettier.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
arrowParen: 'avoid',
|
||||
arrowParens: 'avoid',
|
||||
printWidth: 120,
|
||||
};
|
||||
185
plugins/dbgate-plugin-postgres/src/backend/Analyser.js
Normal file
185
plugins/dbgate-plugin-postgres/src/backend/Analyser.js
Normal file
@@ -0,0 +1,185 @@
|
||||
const fp = require('lodash/fp');
|
||||
const _ = require('lodash');
|
||||
const sql = require('./sql');
|
||||
|
||||
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||
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 Analyser extends DatabaseAnalyser {
|
||||
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,
|
||||
objectId: `tables:${table.schemaName}.${table.pureName}`,
|
||||
columns: columns.rows
|
||||
.filter(col => col.pureName == table.pureName && col.schemaName == table.schemaName)
|
||||
.map(getColumnInfo),
|
||||
primaryKey: DatabaseAnalyser.extractPrimaryKeys(table, pkColumns.rows),
|
||||
foreignKeys: DatabaseAnalyser.extractForeignKeys(table, fkColumns.rows),
|
||||
})),
|
||||
views: views.rows.map(view => ({
|
||||
...view,
|
||||
objectId: `views:${view.schemaName}.${view.pureName}`,
|
||||
columns: columns.rows
|
||||
.filter(col => col.pureName == view.pureName && col.schemaName == view.schemaName)
|
||||
.map(getColumnInfo),
|
||||
})),
|
||||
procedures: routines.rows
|
||||
.filter(x => x.objectType == 'PROCEDURE')
|
||||
.map(proc => ({
|
||||
objectId: `procedures:${proc.schemaName}.${proc.pureName}`,
|
||||
...proc,
|
||||
})),
|
||||
functions: routines.rows
|
||||
.filter(x => x.objectType == 'FUNCTION')
|
||||
.map(func => ({
|
||||
objectId: `functions:${func.schemaName}.${func.pureName}`,
|
||||
...func,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
objectId: `${objectTypeField}:${schemaName}.${pureName}`,
|
||||
}
|
||||
: {
|
||||
newName: { schemaName, pureName },
|
||||
action: 'add',
|
||||
objectTypeField,
|
||||
objectId: `${objectTypeField}:${schemaName}.${pureName}`,
|
||||
};
|
||||
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,
|
||||
objectId: `${objectTypeField}:${x.schemaName}.${x.pureName}`,
|
||||
}));
|
||||
}
|
||||
|
||||
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 = Analyser;
|
||||
213
plugins/dbgate-plugin-postgres/src/backend/driver.js
Normal file
213
plugins/dbgate-plugin-postgres/src/backend/driver.js
Normal file
@@ -0,0 +1,213 @@
|
||||
const _ = require('lodash');
|
||||
const stream = require('stream');
|
||||
const driverBase = require('../frontend/driver');
|
||||
const Analyser = require('./Analyser');
|
||||
const pg = require('pg');
|
||||
const pgQueryStream = require('pg-query-stream');
|
||||
const { createBulkInsertStreamBase, splitPostgresQuery, makeUniqueColumnNames } = require('dbgate-tools');
|
||||
|
||||
function extractPostgresColumns(result) {
|
||||
if (!result || !result.fields) return [];
|
||||
const res = result.fields.map(fld => ({
|
||||
columnName: fld.name,
|
||||
}));
|
||||
makeUniqueColumnNames(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
function zipDataRow(rowArray, columns) {
|
||||
return _.zipObject(
|
||||
columns.map(x => x.columnName),
|
||||
rowArray
|
||||
);
|
||||
}
|
||||
|
||||
async function runStreamItem(client, sql, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = new pgQueryStream(sql, undefined, { rowMode: 'array' });
|
||||
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);
|
||||
resolve();
|
||||
};
|
||||
|
||||
let columns = null;
|
||||
const handleReadable = () => {
|
||||
if (!wasHeader) {
|
||||
columns = extractPostgresColumns(query._result);
|
||||
if (columns && columns.length > 0) {
|
||||
options.recordset(columns);
|
||||
}
|
||||
wasHeader = true;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const row = stream.read();
|
||||
if (!row) break;
|
||||
|
||||
options.row(zipDataRow(row, columns));
|
||||
}
|
||||
};
|
||||
|
||||
// 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',
|
||||
});
|
||||
resolve();
|
||||
};
|
||||
|
||||
stream.on('error', handleError);
|
||||
stream.on('readable', handleReadable);
|
||||
// stream.on('result', handleRow)
|
||||
// stream.on('data', handleRow)
|
||||
stream.on('end', handleEnd);
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
...driverBase,
|
||||
analyserClass: Analyser,
|
||||
|
||||
async connect({ server, port, user, password, database, ssl }) {
|
||||
const client = new pg.Client({
|
||||
host: server,
|
||||
port,
|
||||
user,
|
||||
password,
|
||||
database: database || 'postgres',
|
||||
ssl,
|
||||
});
|
||||
await client.connect();
|
||||
return client;
|
||||
},
|
||||
async query(client, sql) {
|
||||
if (sql == null) {
|
||||
return {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
}
|
||||
const res = await client.query({ text: sql, rowMode: 'array' });
|
||||
const columns = extractPostgresColumns(res);
|
||||
return { rows: res.rows.map(row => zipDataRow(row, columns)), columns };
|
||||
},
|
||||
async stream(client, sql, options) {
|
||||
const sqlSplitted = splitPostgresQuery(sql);
|
||||
|
||||
for (const sqlItem of sqlSplitted) {
|
||||
await runStreamItem(client, sqlItem, options);
|
||||
}
|
||||
|
||||
options.done();
|
||||
// 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 pgQueryStream(sql, undefined, { rowMode: 'array' });
|
||||
|
||||
const queryStream = client.query(query);
|
||||
|
||||
let wasHeader = false;
|
||||
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
});
|
||||
|
||||
const handleEnd = result => {
|
||||
pass.end();
|
||||
};
|
||||
|
||||
let columns = null;
|
||||
const handleReadable = () => {
|
||||
if (!wasHeader) {
|
||||
columns = extractPostgresColumns(query._result);
|
||||
pass.write({
|
||||
__isStreamHeader: true,
|
||||
...(structure || { columns }),
|
||||
});
|
||||
wasHeader = true;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const row = queryStream.read();
|
||||
if (!row) break;
|
||||
|
||||
pass.write(zipDataRow(row, columns));
|
||||
}
|
||||
};
|
||||
|
||||
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 writeTable(pool, name, options) {
|
||||
// @ts-ignore
|
||||
return createBulkInsertStreamBase(this, stream, pool, name, options);
|
||||
},
|
||||
async listDatabases(client) {
|
||||
const { rows } = await this.query(client, 'SELECT datname AS name FROM pg_database WHERE datistemplate = false');
|
||||
return rows;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
6
plugins/dbgate-plugin-postgres/src/backend/index.js
Normal file
6
plugins/dbgate-plugin-postgres/src/backend/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const driver = require('./driver');
|
||||
|
||||
module.exports = {
|
||||
packageName: 'dbgate-plugin-postgres',
|
||||
driver,
|
||||
};
|
||||
19
plugins/dbgate-plugin-postgres/src/backend/sql/columns.js
Normal file
19
plugins/dbgate-plugin-postgres/src/backend/sql/columns.js
Normal file
@@ -0,0 +1,19 @@
|
||||
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
|
||||
`;
|
||||
@@ -0,0 +1,24 @@
|
||||
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
|
||||
`;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user