2020-03-24 18:47:53 +00:00
|
|
|
const _ = require('lodash');
|
|
|
|
const MsSqlAnalyser = require('./MsSqlAnalyser');
|
|
|
|
const MsSqlDumper = require('./MsSqlDumper');
|
2020-01-04 20:59:53 +00:00
|
|
|
|
2020-02-03 18:52:02 +00:00
|
|
|
/** @type {import('@dbgate/types').SqlDialect} */
|
2020-02-02 18:27:25 +00:00
|
|
|
const dialect = {
|
|
|
|
limitSelect: true,
|
2020-03-05 14:04:06 +00:00
|
|
|
rangeSelect: true,
|
|
|
|
offsetFetchRangeSyntax: true,
|
2020-03-12 11:46:07 +00:00
|
|
|
stringEscapeChar: "'",
|
2020-02-02 18:27:25 +00:00
|
|
|
quoteIdentifier(s) {
|
|
|
|
return `[${s}]`;
|
2020-03-24 18:47:53 +00:00
|
|
|
},
|
2020-02-02 18:27:25 +00:00
|
|
|
};
|
|
|
|
|
2020-04-07 19:37:00 +00:00
|
|
|
function extractColumns(columns) {
|
2020-04-13 12:26:14 +00:00
|
|
|
const mapper = {};
|
|
|
|
const res = _.sortBy(_.values(columns), 'index').map((col) => ({
|
2020-04-10 07:50:20 +00:00
|
|
|
...col,
|
|
|
|
columnName: col.name,
|
|
|
|
notNull: !col.nullable,
|
|
|
|
autoIncrement: !!col.identity,
|
|
|
|
}));
|
2020-04-13 12:26:14 +00:00
|
|
|
|
|
|
|
const generateName = () => {
|
|
|
|
let index = 1;
|
|
|
|
while (res.find((x) => x.columnName == `col${index}`)) index += 1;
|
|
|
|
return `col${index}`;
|
|
|
|
};
|
|
|
|
|
|
|
|
// const groups = _.groupBy(res, 'columnName');
|
|
|
|
// for (const colname of _.keys(groups)) {
|
|
|
|
// if (groups[colname].length == 1) continue;
|
|
|
|
// mapper[colname] = [];
|
|
|
|
// for (const col of groups[colname]) {
|
|
|
|
// col.columnName = generateName();
|
|
|
|
// mapper[colname].push(colname);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
for (const col of res) {
|
|
|
|
if (!col.columnName) {
|
|
|
|
const newName = generateName();
|
|
|
|
mapper[col.columnName] = newName;
|
|
|
|
col.columnName = newName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [res, mapper];
|
2020-04-07 19:37:00 +00:00
|
|
|
}
|
|
|
|
|
2020-02-03 18:52:02 +00:00
|
|
|
/** @type {import('@dbgate/types').EngineDriver} */
|
2020-02-02 18:27:25 +00:00
|
|
|
const driver = {
|
2020-02-03 19:34:38 +00:00
|
|
|
async connect(nativeModules, { server, port, user, password, database }) {
|
|
|
|
const pool = await nativeModules.mssql.connect({
|
2020-02-02 21:27:49 +00:00
|
|
|
server,
|
|
|
|
port,
|
|
|
|
user,
|
|
|
|
password,
|
2020-03-05 10:42:16 +00:00
|
|
|
database,
|
2020-04-13 13:20:37 +00:00
|
|
|
requestTimeout: 1000 * 3600,
|
2020-03-05 10:42:16 +00:00
|
|
|
options: {
|
2020-03-24 18:47:53 +00:00
|
|
|
enableArithAbort: true,
|
|
|
|
},
|
2020-02-02 21:27:49 +00:00
|
|
|
});
|
2020-02-03 19:34:38 +00:00
|
|
|
pool._nativeModules = nativeModules;
|
2020-01-04 20:59:53 +00:00
|
|
|
return pool;
|
|
|
|
},
|
2020-04-13 12:26:14 +00:00
|
|
|
// @ts-ignore
|
2020-01-04 20:59:53 +00:00
|
|
|
async query(pool, sql) {
|
|
|
|
const resp = await pool.request().query(sql);
|
2020-01-25 16:26:51 +00:00
|
|
|
// console.log(Object.keys(resp.recordset));
|
2020-03-24 18:47:53 +00:00
|
|
|
// console.log(resp);
|
|
|
|
const res = {};
|
|
|
|
|
|
|
|
if (resp.recordset) {
|
2020-04-13 12:26:14 +00:00
|
|
|
const [columns] = extractColumns(resp.recordset.columns);
|
|
|
|
res.columns = columns;
|
2020-03-24 18:47:53 +00:00
|
|
|
res.rows = resp.recordset;
|
|
|
|
}
|
|
|
|
if (resp.rowsAffected) {
|
|
|
|
res.rowsAffected = _.sum(resp.rowsAffected);
|
|
|
|
}
|
|
|
|
return res;
|
2020-01-04 20:59:53 +00:00
|
|
|
},
|
2020-04-05 18:48:04 +00:00
|
|
|
async stream(pool, sql, options) {
|
|
|
|
const request = await pool.request();
|
2020-04-13 12:26:14 +00:00
|
|
|
let currentMapper = null;
|
2020-04-05 18:48:04 +00:00
|
|
|
|
|
|
|
const handleInfo = (info) => {
|
|
|
|
const { message, lineNumber, procName } = info;
|
|
|
|
options.info({
|
|
|
|
message,
|
|
|
|
line: lineNumber,
|
|
|
|
procedure: procName,
|
|
|
|
time: new Date(),
|
2020-04-10 11:49:41 +00:00
|
|
|
severity: 'info',
|
2020-04-05 18:48:04 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDone = (result) => {
|
2020-04-07 19:37:00 +00:00
|
|
|
// console.log('RESULT', result);
|
|
|
|
options.done(result);
|
2020-04-05 18:48:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const handleRow = (row) => {
|
2020-04-13 12:26:14 +00:00
|
|
|
// if (currentMapper) {
|
|
|
|
// for (const colname of _.keys(currentMapper)) {
|
|
|
|
// let index = 0;
|
|
|
|
// for (const newcolname of currentMapper[colname]) {
|
|
|
|
// row[newcolname] = row[colname][index];
|
|
|
|
// index += 1;
|
|
|
|
// }
|
|
|
|
// delete row[colname];
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
if (currentMapper) {
|
|
|
|
row = { ...row };
|
|
|
|
for (const colname of _.keys(currentMapper)) {
|
|
|
|
const newcolname = currentMapper[colname];
|
|
|
|
row[newcolname] = row[colname];
|
|
|
|
if (_.isArray(row[newcolname])) row[newcolname] = row[newcolname].join(',');
|
|
|
|
delete row[colname];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 19:37:00 +00:00
|
|
|
options.row(row);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleRecordset = (columns) => {
|
2020-04-13 12:26:14 +00:00
|
|
|
const [extractedColumns, mapper] = extractColumns(columns);
|
|
|
|
currentMapper = mapper;
|
|
|
|
options.recordset(extractedColumns);
|
2020-04-05 18:48:04 +00:00
|
|
|
};
|
|
|
|
|
2020-04-10 11:49:41 +00:00
|
|
|
const handleError = (error) => {
|
|
|
|
const { message, lineNumber, procName } = error;
|
|
|
|
options.info({
|
|
|
|
message,
|
|
|
|
line: lineNumber,
|
|
|
|
procedure: procName,
|
|
|
|
time: new Date(),
|
|
|
|
severity: 'error',
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-04-05 18:48:04 +00:00
|
|
|
request.stream = true;
|
2020-04-07 19:37:00 +00:00
|
|
|
request.on('recordset', handleRecordset);
|
2020-04-05 18:48:04 +00:00
|
|
|
request.on('row', handleRow);
|
2020-04-10 11:49:41 +00:00
|
|
|
request.on('error', handleError);
|
2020-04-05 18:48:04 +00:00
|
|
|
request.on('done', handleDone);
|
|
|
|
request.on('info', handleInfo);
|
|
|
|
request.query(sql);
|
2020-04-13 13:20:37 +00:00
|
|
|
|
|
|
|
return request;
|
2020-04-05 18:48:04 +00:00
|
|
|
},
|
2020-06-11 08:23:37 +00:00
|
|
|
async readQuery(pool, sql) {
|
2020-06-07 17:06:21 +00:00
|
|
|
const request = await pool.request();
|
|
|
|
const { stream } = pool._nativeModules;
|
|
|
|
|
|
|
|
const pass = new stream.PassThrough({
|
|
|
|
objectMode: true,
|
|
|
|
highWaterMark: 100,
|
|
|
|
});
|
|
|
|
|
|
|
|
request.stream = true;
|
2020-06-11 08:09:04 +00:00
|
|
|
request.on('recordset', (driverColumns) => {
|
|
|
|
const [columns, mapper] = extractColumns(driverColumns);
|
|
|
|
pass.write({ columns });
|
|
|
|
});
|
2020-06-07 17:06:21 +00:00
|
|
|
request.on('row', (row) => pass.write(row));
|
|
|
|
request.on('error', (err) => {
|
|
|
|
console.error(err);
|
|
|
|
pass.end();
|
|
|
|
});
|
|
|
|
request.on('done', () => pass.end());
|
|
|
|
|
|
|
|
request.query(sql);
|
|
|
|
|
|
|
|
return pass;
|
|
|
|
},
|
2020-01-04 20:59:53 +00:00
|
|
|
async getVersion(pool) {
|
2020-03-24 18:47:53 +00:00
|
|
|
const { version } = (await this.query(pool, 'SELECT @@VERSION AS version')).rows[0];
|
2020-01-04 20:59:53 +00:00
|
|
|
return { version };
|
|
|
|
},
|
|
|
|
async listDatabases(pool) {
|
2020-03-24 18:47:53 +00:00
|
|
|
const { rows } = await this.query(pool, 'SELECT name FROM sys.databases order by name');
|
2020-01-25 16:26:51 +00:00
|
|
|
return rows;
|
2020-01-04 20:59:53 +00:00
|
|
|
},
|
2020-01-06 21:33:20 +00:00
|
|
|
async analyseFull(pool) {
|
2020-01-19 20:01:48 +00:00
|
|
|
const analyser = new MsSqlAnalyser(pool, this);
|
2020-04-12 08:16:33 +00:00
|
|
|
return analyser.fullAnalysis();
|
2020-04-11 18:24:30 +00:00
|
|
|
},
|
|
|
|
async analyseIncremental(pool, structure) {
|
|
|
|
const analyser = new MsSqlAnalyser(pool, this);
|
2020-04-12 08:16:33 +00:00
|
|
|
return analyser.incrementalAnalysis(structure);
|
2020-01-06 21:33:20 +00:00
|
|
|
},
|
2020-02-02 18:27:25 +00:00
|
|
|
createDumper() {
|
|
|
|
return new MsSqlDumper(this);
|
|
|
|
},
|
2020-03-23 19:41:40 +00:00
|
|
|
dialect,
|
|
|
|
engine: 'mssql',
|
2020-01-04 20:59:53 +00:00
|
|
|
};
|
2020-02-02 18:27:25 +00:00
|
|
|
|
|
|
|
module.exports = driver;
|