mirror of https://github.com/Fabio286/antares.git
feat: mysql export for trigger, views, schedulers, functions and routines
This commit is contained in:
parent
0de2321920
commit
b2a5b40c03
|
@ -167,14 +167,12 @@ export default connections => {
|
|||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('export', async (event, { uid, ...rest }) => {
|
||||
ipcMain.handle('export', async (event, { uid, type, tables, ...rest }) => {
|
||||
if (exporter !== null) return;
|
||||
|
||||
const type = connections[uid]._client;
|
||||
|
||||
switch (type) {
|
||||
case 'mysql':
|
||||
exporter = new MysqlExporter(connections[uid], rest);
|
||||
exporter = new MysqlExporter(connections[uid], tables, rest);
|
||||
break;
|
||||
default:
|
||||
return {
|
||||
|
|
|
@ -3,8 +3,9 @@ import path from 'path';
|
|||
import EventEmitter from 'events';
|
||||
|
||||
export class BaseExporter extends EventEmitter {
|
||||
constructor (options) {
|
||||
constructor (tables, options) {
|
||||
super();
|
||||
this._tables = tables;
|
||||
this._options = options;
|
||||
this._isCancelled = false;
|
||||
this._outputStream = fs.createWriteStream(this._options.outputFile, {
|
||||
|
|
|
@ -13,28 +13,36 @@ export default class MysqlExporter extends SqlExporter {
|
|||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
SET NAMES utf8mb4;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE='NO_AUTO_VALUE_ON_ZERO', SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;`;
|
||||
|
||||
return dump;
|
||||
}
|
||||
|
||||
async getFooter () {
|
||||
const footer = await super.getFooter();
|
||||
|
||||
return `/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;`;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
|
||||
${footer}
|
||||
`;
|
||||
}
|
||||
|
||||
async getCreateTable (tableName) {
|
||||
const { rows } = await this._client.raw(`SHOW CREATE TABLE \`${this.schemaName}\`.\`${tableName}\``);
|
||||
const { rows } = await this._client.raw(
|
||||
`SHOW CREATE TABLE \`${this.schemaName}\`.\`${tableName}\``
|
||||
);
|
||||
|
||||
if (rows.length !== 1)
|
||||
return '';
|
||||
if (rows.length !== 1) return '';
|
||||
|
||||
return rows[0]['Create Table'] + ';';
|
||||
const col = 'Create View' in rows[0] ? 'Create View' : 'Create Table';
|
||||
|
||||
return rows[0][col] + ';';
|
||||
}
|
||||
|
||||
getDropTable (tableName) {
|
||||
|
@ -45,54 +53,82 @@ SET NAMES utf8mb4;
|
|||
let rowCount = 0;
|
||||
let sqlStr = '';
|
||||
|
||||
const countResults = await this._client.raw(`SELECT COUNT(1) as count FROM \`${this.schemaName}\`.\`${tableName}\``);
|
||||
if (countResults.rows.length === 1)
|
||||
rowCount = countResults.rows[0].count;
|
||||
const countResults = await this._client.raw(
|
||||
`SELECT COUNT(1) as count FROM \`${this.schemaName}\`.\`${tableName}\``
|
||||
);
|
||||
if (countResults.rows.length === 1) rowCount = countResults.rows[0].count;
|
||||
|
||||
if (rowCount > 0) {
|
||||
const columns = await this._client.getTableColumns({ table: tableName, schema: this.schemaName });
|
||||
let queryLength = 0;
|
||||
let rowsWritten = 0;
|
||||
const { sqlInsertDivider, sqlInsertAfter } = this._options;
|
||||
const columns = await this._client.getTableColumns({
|
||||
table: tableName,
|
||||
schema: this.schemaName
|
||||
});
|
||||
const columnNames = columns.map(col => '`' + col.name + '`');
|
||||
const insertStmt = `INSERT INTO \`${tableName}\` (${columnNames.join(', ')}) VALUES`;
|
||||
const insertStmt = `INSERT INTO \`${tableName}\` (${columnNames.join(
|
||||
', '
|
||||
)}) VALUES`;
|
||||
|
||||
const tableResult = await this._client.raw(`SELECT ${columnNames.join(', ')} FROM \`${this.schemaName}\`.\`${tableName}\``);
|
||||
const tableResult = await this._client.raw(
|
||||
`SELECT ${columnNames.join(', ')} FROM \`${
|
||||
this.schemaName
|
||||
}\`.\`${tableName}\``
|
||||
);
|
||||
|
||||
sqlStr += `LOCK TABLES \`${tableName}\` WRITE;\n`;
|
||||
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` DISABLE KEYS */;`;
|
||||
sqlStr += '\n\n';
|
||||
|
||||
sqlStr += insertStmt;
|
||||
sqlStr += '\n';
|
||||
|
||||
for (const row of tableResult.rows) {
|
||||
sqlStr += '\t(';
|
||||
for (const rowIndex in tableResult.rows) {
|
||||
const row = tableResult.rows[rowIndex];
|
||||
let sqlInsertString = '';
|
||||
|
||||
if (
|
||||
(sqlInsertDivider === 'bytes' &&
|
||||
queryLength >= sqlInsertAfter * 1024) ||
|
||||
(sqlInsertDivider === 'rows' && rowsWritten === sqlInsertAfter)
|
||||
) {
|
||||
sqlInsertString += `;\n${insertStmt}\n\t(`;
|
||||
|
||||
queryLength = 0;
|
||||
rowsWritten = 0;
|
||||
}
|
||||
else if (parseInt(rowIndex) === 0) sqlInsertString += '\n\t(';
|
||||
else sqlInsertString += ',\n\t(';
|
||||
|
||||
for (const i in columns) {
|
||||
const column = columns[i];
|
||||
const val = row[column.name];
|
||||
|
||||
if (val === null)
|
||||
sqlStr += 'NULL';
|
||||
|
||||
else if (BIT.includes(column.type))
|
||||
sqlStr += `b'${hexToBinary(Buffer.from(val).toString('hex'))}'`;
|
||||
|
||||
if (val === null) sqlInsertString += 'NULL';
|
||||
else if (BIT.includes(column.type)) {
|
||||
sqlInsertString += `b'${hexToBinary(
|
||||
Buffer.from(val).toString('hex')
|
||||
)}'`;
|
||||
}
|
||||
else if (BLOB.includes(column.type))
|
||||
sqlStr += `X'${val.toString('hex').toUpperCase()}'`;
|
||||
sqlInsertString += `X'${val.toString('hex').toUpperCase()}'`;
|
||||
else if (val === '') sqlInsertString += '\'\'';
|
||||
else {
|
||||
sqlInsertString +=
|
||||
typeof val === 'string' ? this.escapeAndQuote(val) : val;
|
||||
}
|
||||
|
||||
else if (val === '')
|
||||
sqlStr += '\'\'';
|
||||
|
||||
else
|
||||
sqlStr += typeof val === 'string' ? this.escapeAndQuote(val) : val;
|
||||
|
||||
if (parseInt(i) !== columns.length - 1)
|
||||
sqlStr += ', ';
|
||||
if (parseInt(i) !== columns.length - 1) sqlInsertString += ', ';
|
||||
}
|
||||
|
||||
sqlStr += '),\n';
|
||||
sqlInsertString += ')';
|
||||
sqlStr += sqlInsertString;
|
||||
|
||||
queryLength += sqlInsertString.length;
|
||||
rowsWritten++;
|
||||
}
|
||||
|
||||
sqlStr += '\n';
|
||||
sqlStr += ';\n\n';
|
||||
|
||||
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` ENABLE KEYS */;\n`;
|
||||
sqlStr += 'UNLOCK TABLES;';
|
||||
|
@ -101,6 +137,173 @@ SET NAMES utf8mb4;
|
|||
return sqlStr;
|
||||
}
|
||||
|
||||
async getViews () {
|
||||
const { rows: views } = await this._client.raw(
|
||||
`SHOW TABLE STATUS FROM \`${this.schemaName}\` WHERE Comment = 'VIEW'`
|
||||
);
|
||||
let sqlString = '';
|
||||
|
||||
for (const view of views) {
|
||||
sqlString += `DROP VIEW IF EXISTS \`${view.Name}\`;\n`;
|
||||
const viewSyntax = await this.getCreateTable(view.Name);
|
||||
sqlString += viewSyntax.replaceAll('`' + this.schemaName + '`.', '');
|
||||
sqlString += '\n';
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getTriggers () {
|
||||
const { rows: triggers } = await this._client.raw(
|
||||
`SHOW TRIGGERS FROM \`${this.schemaName}\``
|
||||
);
|
||||
const generatedTables = this._tables
|
||||
.filter(t => t.includeStructure)
|
||||
.map(t => t.table);
|
||||
|
||||
let sqlString = '';
|
||||
|
||||
for (const trigger of triggers) {
|
||||
const {
|
||||
Trigger: name,
|
||||
Timing: timing,
|
||||
Event: event,
|
||||
Table: table,
|
||||
Statement: statement,
|
||||
sql_mode: sqlMode
|
||||
} = trigger;
|
||||
|
||||
if (!generatedTables.includes(table)) continue;
|
||||
|
||||
const definer = this.getEscapedDefiner(trigger.Definer);
|
||||
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||
sqlString += `/*!50003 SET SQL_MODE="${sqlMode}" */;\n`;
|
||||
sqlString += 'DELIMITER ;;\n';
|
||||
sqlString += '/*!50003 CREATE*/ ';
|
||||
sqlString += `/*!50017 DEFINER=${definer}*/ `;
|
||||
sqlString += `/*!50003 TRIGGER \`${name}\` ${timing} ${event} ON ${table} FOR EACH ROW ${statement}*/;;\n`;
|
||||
sqlString += 'DELIMITER ;\n';
|
||||
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE */;\n\n';
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getSchedulers () {
|
||||
const { rows: schedulers } = await this._client.raw(
|
||||
`SELECT *, EVENT_SCHEMA AS \`Db\`, EVENT_NAME AS \`Name\` FROM information_schema.\`EVENTS\` WHERE EVENT_SCHEMA = '${this.schemaName}'`
|
||||
);
|
||||
let sqlString = '';
|
||||
|
||||
for (const scheduler of schedulers) {
|
||||
const {
|
||||
EVENT_NAME: name,
|
||||
SQL_MODE: sqlMode,
|
||||
EVENT_TYPE: type,
|
||||
INTERVAL_VALUE: intervalValue,
|
||||
INTERVAL_FIELD: intervalField,
|
||||
STARTS: starts,
|
||||
ENDS: ends,
|
||||
EXECUTE_AT: at,
|
||||
ON_COMPLETION: onCompletion,
|
||||
STATUS: status,
|
||||
EVENT_DEFINITION: definition
|
||||
} = scheduler;
|
||||
|
||||
const definer = this.getEscapedDefiner(scheduler.DEFINER);
|
||||
const comment = this.escapeAndQuote(scheduler.EVENT_COMMENT);
|
||||
|
||||
sqlString += `/*!50106 DROP EVENT IF EXISTS \`${name}\` */;\n`;
|
||||
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||
sqlString += `/*!50003 SET SQL_MODE='${sqlMode}' */;\n`;
|
||||
sqlString += 'DELIMITER ;;\n';
|
||||
sqlString += '/*!50106 CREATE*/ ';
|
||||
sqlString += `/*!50117 DEFINER=${definer}*/ `;
|
||||
sqlString += `/*!50106 EVENT \`${name}\` ON SCHEDULE `;
|
||||
if (type === 'RECURRING') {
|
||||
sqlString += `EVERY ${intervalValue} ${intervalField} STARTS '${starts}' `;
|
||||
|
||||
if (ends) sqlString += `ENDS '${ends}' `;
|
||||
}
|
||||
else sqlString += `AT '${at}' `;
|
||||
sqlString += `ON COMPLETION ${onCompletion} ${
|
||||
status === 'disabled' ? 'DISABLE' : 'ENABLE'
|
||||
} COMMENT ${comment || '\'\''} DO ${definition}*/;;\n`;
|
||||
sqlString += 'DELIMITER ;\n';
|
||||
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE*/;;\n';
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getFunctions () {
|
||||
const { rows: functions } = await this._client.raw(
|
||||
`SHOW FUNCTION STATUS WHERE \`Db\` = '${this.schemaName}';`
|
||||
);
|
||||
|
||||
let sqlString = '';
|
||||
|
||||
for (const func of functions) {
|
||||
sqlString += await this.getRoutineSyntax(
|
||||
func.Name,
|
||||
func.Type,
|
||||
func.Definer
|
||||
);
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getRoutines () {
|
||||
const { rows: routines } = await this._client.raw(
|
||||
`SHOW PROCEDURE STATUS WHERE \`Db\` = '${this.schemaName}';`
|
||||
);
|
||||
|
||||
let sqlString = '';
|
||||
|
||||
for (const routine of routines) {
|
||||
sqlString += await this.getRoutineSyntax(
|
||||
routine.Name,
|
||||
routine.Type,
|
||||
routine.Definer
|
||||
);
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getRoutineSyntax (name, type, definer) {
|
||||
const { rows: routines } = await this._client.raw(
|
||||
`SHOW CREATE ${type} \`${this.schemaName}\`.\`${name}\``
|
||||
);
|
||||
|
||||
if (routines.length === 0) return '';
|
||||
|
||||
const routine = routines[0];
|
||||
|
||||
const { sql_mode: sqlMode, 'Create Function': createProcedure } = routine;
|
||||
|
||||
const startOffset = createProcedure.indexOf(type);
|
||||
const procedureBody = createProcedure.substring(startOffset);
|
||||
|
||||
let sqlString = 'DELIMITER ;;\n';
|
||||
sqlString = `/*!50003 DROP ${type} IF EXISTS ${name}*/;;\n`;
|
||||
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||
sqlString += `/*!50003 SET SQL_MODE="${sqlMode}"*/;;\n`;
|
||||
sqlString += `/*!50003 CREATE*/ /*!50020 DEFINER=${definer}*/ /*!50003 ${procedureBody}*/;;\n`;
|
||||
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE*/;;\n';
|
||||
sqlString += 'DELIMITER ;\n';
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
getEscapedDefiner (definer) {
|
||||
return definer
|
||||
.split('@')
|
||||
.map(part => '`' + part + '`')
|
||||
.join('@');
|
||||
}
|
||||
|
||||
escapeAndQuote (value) {
|
||||
if (!value) return null;
|
||||
return `'${value.replaceAll(/'/g, '\'\'')}'`;
|
||||
|
|
|
@ -3,8 +3,8 @@ import moment from 'moment';
|
|||
import { BaseExporter } from '../BaseExporter';
|
||||
|
||||
export class SqlExporter extends BaseExporter {
|
||||
constructor (client, options) {
|
||||
super(options);
|
||||
constructor (client, tables, options) {
|
||||
super(tables, options);
|
||||
this._client = client;
|
||||
this._commentChar = '#';
|
||||
}
|
||||
|
@ -23,8 +23,15 @@ export class SqlExporter extends BaseExporter {
|
|||
}
|
||||
|
||||
async dump () {
|
||||
const { includes } = this._options;
|
||||
const extraItems = Object.keys(includes).filter(key => includes[key]);
|
||||
const totalTableToProcess = this._tables.filter(
|
||||
t => t.includeStructure || t.includeContent || t.includeDropStatement
|
||||
).length;
|
||||
const processingItemCount = totalTableToProcess + extraItems.length;
|
||||
|
||||
const exportState = {
|
||||
totalItems: this._options.items.length,
|
||||
totalItems: processingItemCount,
|
||||
currentItemIndex: 0,
|
||||
currentItem: '',
|
||||
op: ''
|
||||
|
@ -34,13 +41,16 @@ export class SqlExporter extends BaseExporter {
|
|||
this.writeString(header);
|
||||
this.writeString('\n\n\n');
|
||||
|
||||
for (const item of this._options.items) {
|
||||
for (const item of this._tables) {
|
||||
// user abort operation
|
||||
if (this.isCancelled)
|
||||
return;
|
||||
if (this.isCancelled) return;
|
||||
|
||||
// skip item if not set to output any detail for them
|
||||
if (!item.includeStructure && !item.includeContent && !item.includeDropStatement)
|
||||
if (
|
||||
!item.includeStructure &&
|
||||
!item.includeContent &&
|
||||
!item.includeDropStatement
|
||||
)
|
||||
continue;
|
||||
|
||||
exportState.currentItemIndex++;
|
||||
|
@ -49,7 +59,9 @@ export class SqlExporter extends BaseExporter {
|
|||
|
||||
this.emitUpdate(exportState);
|
||||
|
||||
const tableHeader = this.buildComment(`Dump of table ${item.table}\n------------------------------------------------------------`);
|
||||
const tableHeader = this.buildComment(
|
||||
`Dump of table ${item.table}\n------------------------------------------------------------`
|
||||
);
|
||||
this.writeString(tableHeader);
|
||||
this.writeString('\n\n');
|
||||
|
||||
|
@ -79,12 +91,38 @@ export class SqlExporter extends BaseExporter {
|
|||
this.writeString('\n\n');
|
||||
}
|
||||
|
||||
for (const item of extraItems) {
|
||||
const processingMethod = `get${item.charAt(0).toUpperCase() +
|
||||
item.slice(1)}`;
|
||||
exportState.currentItemIndex++;
|
||||
exportState.currentItem = item;
|
||||
exportState.op = 'PROCESSING';
|
||||
this.emitUpdate(exportState);
|
||||
|
||||
if (this[processingMethod]) {
|
||||
const data = await this[processingMethod]();
|
||||
if (data !== '') {
|
||||
const header =
|
||||
this.buildComment(
|
||||
`Dump of ${item}\n------------------------------------------------------------`
|
||||
) + '\n\n';
|
||||
|
||||
this.writeString(header);
|
||||
this.writeString(data);
|
||||
this.writeString('\n\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const footer = await this.getFooter();
|
||||
this.writeString(footer);
|
||||
}
|
||||
|
||||
buildComment (text) {
|
||||
return text.split('\n').map(txt => `${this._commentChar} ${txt}`).join('\n');
|
||||
return text
|
||||
.split('\n')
|
||||
.map(txt => `${this._commentChar} ${txt}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
async getSqlHeader () {
|
||||
|
@ -105,11 +143,13 @@ Generation time: ${moment().format()}
|
|||
}
|
||||
|
||||
async getFooter () {
|
||||
return '';
|
||||
return this.buildComment(`Dump completed on ${moment().format()}`);
|
||||
}
|
||||
|
||||
getCreateTable (tableName) {
|
||||
throw new Error('Sql Exporter must implement the "getCreateTable" method');
|
||||
throw new Error(
|
||||
'Sql Exporter must implement the "getCreateTable" method'
|
||||
);
|
||||
}
|
||||
|
||||
getDropTable (tableName) {
|
||||
|
@ -117,6 +157,8 @@ Generation time: ${moment().format()}
|
|||
}
|
||||
|
||||
getTableInsert (tableName) {
|
||||
throw new Error('Sql Exporter must implement the "getTableInsert" method');
|
||||
throw new Error(
|
||||
'Sql Exporter must implement the "getTableInsert" method'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,111 +37,168 @@
|
|||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns mb-2">
|
||||
<div class="column col-auto d-flex p-0 text-italic ">
|
||||
<i class="mdi mdi-file-document-outline mr-2" />
|
||||
{{ filename }}
|
||||
</div>
|
||||
|
||||
<div class="column col-auto col-ml-auto p-0">
|
||||
<button class="btn btn-dark btn-sm" @click="uncheckAllTables">
|
||||
<i class="mdi mdi-file-tree-outline" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" @click="checkAllTables">
|
||||
<i class="mdi mdi-file-tree" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="workspace-query-results">
|
||||
<div ref="table" class="table table-hover">
|
||||
<div class="thead">
|
||||
<div class="tr">
|
||||
<div class="th c-hand" style="width: 50%;">
|
||||
<div class="table-column-title">
|
||||
<span>Table</span>
|
||||
<div class="columns export-options">
|
||||
<div class="column col-8 left">
|
||||
<div class="columns mb-2">
|
||||
<div class="column col-auto d-flex text-italic ">
|
||||
<i class="mdi mdi-file-document-outline mr-2" />
|
||||
{{ filename }}
|
||||
</div>
|
||||
|
||||
<div class="column col-auto col-ml-auto ">
|
||||
<button class="btn btn-dark btn-sm" @click="refresh">
|
||||
<i class="mdi mdi-database-refresh" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="isRefreshing"
|
||||
@click="uncheckAllTables"
|
||||
>
|
||||
<i class="mdi mdi-file-tree-outline" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="isRefreshing"
|
||||
@click="checkAllTables"
|
||||
>
|
||||
<i class="mdi mdi-file-tree" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results">
|
||||
<div ref="table" class="table table-hover">
|
||||
<div class="thead">
|
||||
<div class="tr">
|
||||
<div class="th c-hand" style="width: 50%;">
|
||||
<div class="table-column-title">
|
||||
<span>{{ $t('word.table') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th c-hand text-center">
|
||||
<div class="table-column-title">
|
||||
<span>{{ $t('word.structure') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th c-hand text-center">
|
||||
<div class="table-column-title">
|
||||
<span>{{ $t('word.content') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th c-hand text-center">
|
||||
<div class="table-column-title">
|
||||
<span>{{ $t('word.drop') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th c-hand">
|
||||
<div class="table-column-title">
|
||||
<span>Structure</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th c-hand">
|
||||
<div class="table-column-title">
|
||||
<span>Content</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th c-hand">
|
||||
<div class="table-column-title">
|
||||
<span>Drop</span>
|
||||
|
||||
<div class="tbody">
|
||||
<div
|
||||
v-for="item in tables"
|
||||
:key="item.name"
|
||||
class="tr"
|
||||
>
|
||||
<div class="td">
|
||||
{{ item.table }}
|
||||
</div>
|
||||
<div class="td text-center">
|
||||
<label class="form-checkbox m-0 px-2 form-inline">
|
||||
<input
|
||||
v-model="item.includeStructure"
|
||||
type="checkbox"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td text-center">
|
||||
<label class="form-checkbox m-0 px-2 form-inline">
|
||||
<input
|
||||
v-model="item.includeContent"
|
||||
type="checkbox"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td text-center">
|
||||
<label class="form-checkbox m-0 px-2 form-inline">
|
||||
<input
|
||||
v-model="item.includeDropStatement"
|
||||
type="checkbox"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-4">
|
||||
<h4>
|
||||
{{ $t('word.options') }}
|
||||
</h4>
|
||||
<span>{{ $t('word.includes') }}:</span>
|
||||
|
||||
<div class="tbody">
|
||||
<div
|
||||
v-for="item in tables"
|
||||
:key="item.name"
|
||||
class="tr"
|
||||
>
|
||||
<div class="td">
|
||||
{{ item.table }}
|
||||
</div>
|
||||
<div class="td">
|
||||
<label class="form-checkbox m-0 px-2">
|
||||
<input
|
||||
v-model="item.includeStructure"
|
||||
type="checkbox"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td">
|
||||
<label class="form-checkbox m-0 px-2">
|
||||
<input
|
||||
v-model="item.includeContent"
|
||||
type="checkbox"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td">
|
||||
<label class="form-checkbox m-0 px-2">
|
||||
<input
|
||||
v-model="item.includeDropStatement"
|
||||
type="checkbox"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<label
|
||||
v-for="(_, key) in options.includes"
|
||||
:key="key"
|
||||
class="form-checkbox"
|
||||
>
|
||||
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $t(`word.${key}`) }}
|
||||
</label>
|
||||
|
||||
<div class="mt-4 mb-2">
|
||||
{{ $t('message.newInserStmtEvery') }}:
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-6">
|
||||
<input
|
||||
v-model.number="options.sqlInsertAfter"
|
||||
type="number"
|
||||
class="form-input"
|
||||
value="250"
|
||||
>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<select v-model="options.sqlInsertDivider" class="form-select">
|
||||
<option value="bytes">
|
||||
KiB
|
||||
</option>
|
||||
<option value="rows">
|
||||
{{ $t('word.rows') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="progressPercentage > 0">
|
||||
<progress
|
||||
class="progress"
|
||||
:value="progressPercentage"
|
||||
max="100"
|
||||
/>
|
||||
<p class="empty-subtitle">
|
||||
{{ progressPercentage }}% - {{ progressStatus }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
:class="{'loading': isExporting}"
|
||||
:disabled="isExporting"
|
||||
@click.stop="startExport"
|
||||
>
|
||||
{{ $t('word.export') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
<div class="modal-footer columns">
|
||||
<div class="column col modal-progress-wrapper text-left">
|
||||
<div v-if="progressPercentage > 0" class="export-progress">
|
||||
<span class="progress-status">
|
||||
{{ progressPercentage }}% - {{ progressStatus }}
|
||||
</span>
|
||||
<progress
|
||||
class="progress d-block"
|
||||
:value="progressPercentage"
|
||||
max="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-auto px-0">
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
:class="{'loading': isExporting}"
|
||||
:disabled="isExporting || isRefreshing"
|
||||
autofocus
|
||||
@click.prevent="startExport"
|
||||
>
|
||||
{{ $t('word.export') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -151,6 +208,7 @@
|
|||
import { ipcRenderer } from 'electron';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import moment from 'moment';
|
||||
import customizations from 'common/customizations';
|
||||
import Application from '@/ipc-api/Application';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
|
||||
|
@ -163,9 +221,15 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
isExporting: false,
|
||||
isRefreshing: false,
|
||||
progressPercentage: 0,
|
||||
progressStatus: '',
|
||||
tables: [],
|
||||
options: {
|
||||
includes: {},
|
||||
sqlInsertAfter: 250,
|
||||
sqlInsertDivider: 'bytes'
|
||||
},
|
||||
basePath: ''
|
||||
};
|
||||
},
|
||||
|
@ -194,7 +258,7 @@ export default {
|
|||
}
|
||||
},
|
||||
async created () {
|
||||
await this.refreshSchema({ uid: this.currentWorkspace.uid, schema: this.selectedSchema });
|
||||
await this.refresh();
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
this.basePath = await Application.getDownloadPathDirectory();
|
||||
this.tables = this.schemaItems.map(item => ({
|
||||
|
@ -204,6 +268,14 @@ export default {
|
|||
includeDropStatement: true
|
||||
}));
|
||||
|
||||
const structure = ['views', 'triggers', 'routines', 'functions', 'schedulers', 'triggerFunctions'];
|
||||
|
||||
structure.forEach(feat => {
|
||||
const val = customizations[this.currentWorkspace.client][feat];
|
||||
if (val)
|
||||
this.$set(this.options.includes, feat, true);
|
||||
});
|
||||
|
||||
ipcRenderer.on('export-progress', this.updateProgress);
|
||||
},
|
||||
beforeDestroy () {
|
||||
|
@ -216,18 +288,20 @@ export default {
|
|||
}),
|
||||
async startExport () {
|
||||
this.isExporting = true;
|
||||
const { uid } = this.currentWorkspace;
|
||||
const { uid, client } = this.currentWorkspace;
|
||||
const params = {
|
||||
uid,
|
||||
type: client,
|
||||
schema: this.selectedSchema,
|
||||
outputFile: this.dumpFilePath,
|
||||
items: [...this.tables]
|
||||
tables: [...this.tables],
|
||||
...this.options
|
||||
};
|
||||
|
||||
const result = await Schema.export(params);
|
||||
if (result) {
|
||||
if (result.status === 'success')
|
||||
this.progressStatus = result.response.cancelled ? 'Aborted' : 'Completed!';
|
||||
this.progressStatus = result.response.cancelled ? this.$t('word.aborted') : this.$t('word.completed');
|
||||
|
||||
else
|
||||
this.progressStatus = result.response;
|
||||
|
@ -237,7 +311,17 @@ export default {
|
|||
},
|
||||
updateProgress (event, state) {
|
||||
this.progressPercentage = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
|
||||
this.progressStatus = state.op + ' ' + state.currentItem;
|
||||
switch (state.op) {
|
||||
case 'PROCESSING':
|
||||
this.progressStatus = this.$t('message.processingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
case 'FETCH':
|
||||
this.progressStatus = this.$t('message.fechingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
case 'WRITE':
|
||||
this.progressStatus = this.$t('message.writingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
}
|
||||
},
|
||||
async closeModal () {
|
||||
let willClose = true;
|
||||
|
@ -261,6 +345,11 @@ export default {
|
|||
uncheckAllTables () {
|
||||
this.tables = this.tables.map(item => ({ table: item.table, includeStructure: false, includeContent: false, includeDropStatement: false }));
|
||||
},
|
||||
async refresh () {
|
||||
this.isRefreshing = true;
|
||||
await this.refreshSchema({ uid: this.currentWorkspace.uid, schema: this.selectedSchema });
|
||||
this.isRefreshing = false;
|
||||
},
|
||||
async openPathDialog () {
|
||||
const result = await Application.showOpenDialog({ properties: ['openDirectory'] });
|
||||
if (result && !result.canceled)
|
||||
|
@ -271,9 +360,19 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.workspace-query-results {
|
||||
flex: 1 1 auto;
|
||||
.export-options {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-results {
|
||||
flex: 1 0 1px;
|
||||
.table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
@ -292,13 +391,22 @@ export default {
|
|||
|
||||
.modal-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
height: 60vh;
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -122,7 +122,17 @@ module.exports = {
|
|||
select: 'Select',
|
||||
passphrase: 'Passphrase',
|
||||
filter: 'Filter',
|
||||
change: 'Change'
|
||||
change: 'Change',
|
||||
views: 'Views',
|
||||
triggers: 'Triggers',
|
||||
routines: 'Routines',
|
||||
functions: 'Functions',
|
||||
schedulers: 'Schedulers',
|
||||
includes: 'Includes',
|
||||
drop: 'Drop',
|
||||
rows: 'Rows',
|
||||
completed: 'Completed',
|
||||
aborted: 'Aborted'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
|
@ -145,10 +155,12 @@ module.exports = {
|
|||
downloadingUpdate: 'Downloading update',
|
||||
updateDownloaded: 'Update downloaded',
|
||||
restartToInstall: 'Restart Antares to install',
|
||||
unableEditFieldWithoutPrimary: 'Unable to edit a field without a primary key in resultset',
|
||||
unableEditFieldWithoutPrimary:
|
||||
'Unable to edit a field without a primary key in resultset',
|
||||
editCell: 'Edit cell',
|
||||
deleteRows: 'Delete row | Delete {count} rows',
|
||||
confirmToDeleteRows: 'Do you confirm to delete one row? | Do you confirm to delete {count} rows?',
|
||||
confirmToDeleteRows:
|
||||
'Do you confirm to delete one row? | Do you confirm to delete {count} rows?',
|
||||
notificationsTimeout: 'Notifications timeout',
|
||||
uploadFile: 'Upload file',
|
||||
addNewRow: 'Add new row',
|
||||
|
@ -176,7 +188,8 @@ module.exports = {
|
|||
deleteTable: 'Delete table',
|
||||
emptyCorfirm: 'Do you confirm to empty',
|
||||
unsavedChanges: 'Unsaved changes',
|
||||
discardUnsavedChanges: 'You have some unsaved changes. Closing this tab these changes will be discarded.',
|
||||
discardUnsavedChanges:
|
||||
'You have some unsaved changes. Closing this tab these changes will be discarded.',
|
||||
thereAreNoIndexes: 'There are no indexes',
|
||||
thereAreNoForeign: 'There are no foreign keys',
|
||||
createNewForeign: 'Create new foreign key',
|
||||
|
@ -249,7 +262,11 @@ module.exports = {
|
|||
killProcess: 'Kill process',
|
||||
closeTab: 'Close tab',
|
||||
exportSchema: 'Export schema',
|
||||
directoryPath: 'Directory path'
|
||||
directoryPath: 'Directory path',
|
||||
newInserStmtEvery: 'New INSERT statement every',
|
||||
processingTableExport: 'Processing {table}',
|
||||
fechingTableExport: 'Fetching {table} data',
|
||||
writingTableExport: 'Writing {table} data'
|
||||
},
|
||||
faker: {
|
||||
address: 'Address',
|
||||
|
|
|
@ -122,7 +122,17 @@ module.exports = {
|
|||
select: 'Seleziona',
|
||||
passphrase: 'Passphrase',
|
||||
filter: 'Filtra',
|
||||
change: 'Cambia'
|
||||
change: 'Cambia',
|
||||
views: 'Viste',
|
||||
triggers: 'Trigger',
|
||||
routines: 'Routine',
|
||||
functions: 'Function',
|
||||
schedulers: 'Scheduler',
|
||||
includes: 'Includi',
|
||||
drop: 'Drop',
|
||||
rows: 'Righe',
|
||||
completed: 'Completato',
|
||||
aborted: 'Annullato'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Benvenuto in Antares SQL Client!',
|
||||
|
@ -145,10 +155,12 @@ module.exports = {
|
|||
downloadingUpdate: 'Download dell\'aggiornamento',
|
||||
updateDownloaded: 'Aggiornamento scaricato',
|
||||
restartToInstall: 'Riavvia Antares per installare l\'aggiornamento',
|
||||
unableEditFieldWithoutPrimary: 'Impossibile modificare il campo senza una primary key nel resultset',
|
||||
unableEditFieldWithoutPrimary:
|
||||
'Impossibile modificare il campo senza una primary key nel resultset',
|
||||
editCell: 'Modifica cella',
|
||||
deleteRows: 'Elimina riga | Elimina {count} righe',
|
||||
confirmToDeleteRows: 'Confermi di voler cancellare una riga? | Confermi di voler cancellare {count} righe?',
|
||||
confirmToDeleteRows:
|
||||
'Confermi di voler cancellare una riga? | Confermi di voler cancellare {count} righe?',
|
||||
notificationsTimeout: 'Timeout Notifiche',
|
||||
uploadFile: 'Carica file',
|
||||
addNewRow: 'Aggiungi nuova riga',
|
||||
|
@ -176,7 +188,8 @@ module.exports = {
|
|||
deleteTable: 'Cancella tabella',
|
||||
emptyCorfirm: 'Confermi di voler svuotare',
|
||||
unsavedChanges: 'Modifiche non salvate',
|
||||
discardUnsavedChanges: 'Hai modifiche non salvate. Lasciando questa scheda le modifiche saranno scartate.',
|
||||
discardUnsavedChanges:
|
||||
'Hai modifiche non salvate. Lasciando questa scheda le modifiche saranno scartate.',
|
||||
thereAreNoIndexes: 'Non ci sono indici',
|
||||
thereAreNoForeign: 'Non ci sono chiavi esterne',
|
||||
createNewForeign: 'Crea nuova chiave esterna',
|
||||
|
@ -236,7 +249,11 @@ module.exports = {
|
|||
noSchema: 'Nessuno schema',
|
||||
restorePreviourSession: 'Ripristina sessione precedente',
|
||||
exportSchema: 'Esporta schema',
|
||||
directoryPath: 'Percorso directory'
|
||||
directoryPath: 'Percorso directory',
|
||||
newInserStmtEvery: 'Nuova istruzione INSERT ogni',
|
||||
processingTableExport: 'Processo {table}',
|
||||
fechingTableExport: 'Ricavo i dati {table}',
|
||||
writingTableExport: 'Scrittura dati {table}'
|
||||
},
|
||||
faker: {
|
||||
address: 'Indirizzo',
|
||||
|
|
Loading…
Reference in New Issue