-
-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: initial db export implementation
- Loading branch information
Showing
15 changed files
with
833 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import EventEmitter from 'events'; | ||
|
||
export class BaseExporter extends EventEmitter { | ||
constructor (options) { | ||
super(); | ||
this._options = options; | ||
this._isCancelled = false; | ||
this._outputStream = fs.createWriteStream(this._options.outputFile, { | ||
flags: 'w' | ||
}); | ||
this._state = {}; | ||
|
||
this._outputStream.once('error', err => { | ||
this._isCancelled = true; | ||
this.emit('error', err); | ||
}); | ||
} | ||
|
||
async run () { | ||
try { | ||
this.emit('start', this); | ||
await this.dump(); | ||
} | ||
catch (err) { | ||
this.emit('error', err); | ||
throw err; | ||
} | ||
finally { | ||
this._outputStream.end(); | ||
this.emit('end'); | ||
} | ||
} | ||
|
||
get isCancelled () { | ||
return this._isCancelled; | ||
} | ||
|
||
outputFileExists () { | ||
return fs.existsSync(this._options.outputFile); | ||
} | ||
|
||
cancel () { | ||
this._isCancelled = true; | ||
this.emit('cancel'); | ||
this.emitUpdate({ op: 'cancelling' }); | ||
} | ||
|
||
emitUpdate (state) { | ||
this.emit('progress', { ...this._state, ...state }); | ||
} | ||
|
||
writeString (data) { | ||
if (this._isCancelled) return; | ||
|
||
try { | ||
fs.accessSync(this._options.outputFile); | ||
} | ||
catch (err) { | ||
this._isCancelled = true; | ||
|
||
const fileName = path.basename(this._options.outputFile); | ||
this.emit('error', `The file ${fileName} is not accessible`); | ||
} | ||
|
||
this._outputStream.write(data); | ||
} | ||
|
||
dump () { | ||
throw new Error('Exporter must implement the "dump" method'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { MysqlExporter } from './sql/MysqlExporter'; | ||
|
||
export class ExporterFactory { | ||
/** | ||
* Returns a data exporter class instance. | ||
* | ||
* @param {Object} args | ||
* @param {String} args.client | ||
* @param {Object} args.params | ||
* @param {String} args.params.host | ||
* @param {Number} args.params.port | ||
* @param {String} args.params.password | ||
* @param {String=} args.params.database | ||
* @param {String=} args.params.schema | ||
* @param {String} args.params.ssh.host | ||
* @param {String} args.params.ssh.username | ||
* @param {String} args.params.ssh.password | ||
* @param {Number} args.params.ssh.port | ||
* @param {Number=} args.poolSize | ||
* @returns Exporter Instance | ||
* @memberof ExporterFactory | ||
*/ | ||
static get (args) { | ||
switch (type) { | ||
case 'mysql': | ||
exporter = new MysqlExporter(connections[uid], rest); | ||
break; | ||
default: | ||
return { | ||
status: 'error', | ||
response: `${type} exporter not aviable` | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { SqlExporter } from './SqlExporter'; | ||
import { BLOB, BIT } from 'common/fieldTypes'; | ||
import hexToBinary from 'common/libs/hexToBinary'; | ||
|
||
export default class MysqlExporter extends SqlExporter { | ||
async getSqlHeader () { | ||
let dump = await super.getSqlHeader(); | ||
dump += ` | ||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; | ||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; | ||
/*!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' */; | ||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;`; | ||
|
||
return dump; | ||
} | ||
|
||
async 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 */;`; | ||
} | ||
|
||
async getCreateTable (tableName) { | ||
const { rows } = await this._client.raw(`SHOW CREATE TABLE \`${this.schemaName}\`.\`${tableName}\``); | ||
|
||
if (rows.length !== 1) | ||
return ''; | ||
|
||
return rows[0]['Create Table'] + ';'; | ||
} | ||
|
||
getDropTable (tableName) { | ||
return `DROP TABLE IF EXISTS \`${tableName}\`;`; | ||
} | ||
|
||
async getTableInsert (tableName) { | ||
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; | ||
|
||
if (rowCount > 0) { | ||
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 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 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'))}'`; | ||
|
||
else if (BLOB.includes(column.type)) | ||
sqlStr += `X'${val.toString('hex').toUpperCase()}'`; | ||
|
||
else if (val === '') | ||
sqlStr += '\'\''; | ||
|
||
else | ||
sqlStr += typeof val === 'string' ? this.escapeAndQuote(val) : val; | ||
|
||
if (parseInt(i) !== columns.length - 1) | ||
sqlStr += ', '; | ||
} | ||
|
||
sqlStr += '),\n'; | ||
} | ||
|
||
sqlStr += '\n'; | ||
|
||
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` ENABLE KEYS */;\n`; | ||
sqlStr += 'UNLOCK TABLES;'; | ||
} | ||
|
||
return sqlStr; | ||
} | ||
|
||
escapeAndQuote (value) { | ||
if (!value) return null; | ||
return `'${value.replaceAll(/'/g, '\'\'')}'`; | ||
} | ||
} |
Oops, something went wrong.