mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-18 11:26:00 +00:00
added plugins
This commit is contained in:
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,
|
||||
};
|
||||
Reference in New Issue
Block a user