added plugins

This commit is contained in:
Jan Prochazka
2021-04-13 16:17:53 +02:00
parent 446e7c139f
commit 4d5cc119f2
115 changed files with 5519 additions and 24 deletions

View 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;

View File

@@ -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;

View File

@@ -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;

View 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;

View 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;

View File

@@ -0,0 +1,9 @@
const driver = require('./driver');
module.exports = {
packageName: 'dbgate-plugin-mssql',
driver,
initialize(dbgateEnv) {
driver.initialize(dbgateEnv);
},
};

View File

@@ -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;

View 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,
};

View 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
`;

View 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]
`;

View File

@@ -0,0 +1 @@
module.exports = `select schema_id as objectId, name as schemaName from sys.schemas`;

View 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,
};

View File

@@ -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
`;

View File

@@ -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
`;

View 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]
`;

View File

@@ -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]
`;

View 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]
`;

View 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
`;

View 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]
`;

View 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,
};

View 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;

View 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;

View File

@@ -0,0 +1,5 @@
import driver from './driver';
export default {
driver,
};