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,21 @@
MIT License
Copyright (c) 2021 Jan Prochazka
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,7 @@
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://paypal.me/JanProchazkaCz/30eur)
[![NPM version](https://img.shields.io/npm/v/dbgate-plugin-mssql.svg)](https://www.npmjs.com/package/dbgate-plugin-mssql)
# dbgate-plugin-mssql
MS SQL connector plugin for DbGate

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,39 @@
{
"name": "dbgate-plugin-mssql",
"main": "dist/backend.js",
"version": "1.2.2",
"homepage": "https://github.com/dbgate/dbgate-plugin-mssql",
"description": "MS SQL connect plugin for DbGate",
"repository": {
"type": "git",
"url": "https://github.com/dbgate/dbgate-plugin-mssql.git"
},
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
"author": "Jan Prochazka",
"license": "GPL",
"keywords": [
"sql",
"mssql",
"dbgate",
"dbgateplugin"
],
"files": [
"dist"
],
"scripts": {
"build:frontend": "webpack --config webpack-frontend.config",
"build:backend": "webpack --config webpack-backend.config.js",
"build": "yarn build:frontend && yarn build:backend",
"prepublishOnly": "yarn build",
"plugin": "yarn build && yarn pack && dbgate-plugin dbgate-plugin-mssql",
"plugout": "dbgate-plugout dbgate-plugin-mssql"
},
"devDependencies": {
"async-lock": "^1.2.6",
"dbgate-plugin-tools": "^1.0.4",
"dbgate-tools": "^4.0.3-rc.1",
"tedious": "^9.2.3",
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
}
}

View File

@@ -0,0 +1,9 @@
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
semi: true,
singleQuote: true,
arrowParen: 'avoid',
arrowParens: 'avoid',
printWidth: 120,
};

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

View File

@@ -0,0 +1,22 @@
var webpack = require('webpack');
var path = require('path');
var config = {
context: __dirname + '/src/backend',
entry: {
app: './index.js',
},
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'backend.js',
libraryTarget: 'commonjs2',
},
// optimization: {
// minimize: false,
// },
};
module.exports = config;

View File

@@ -0,0 +1,23 @@
var webpack = require("webpack");
var path = require("path");
var config = {
context: __dirname + "/src/frontend",
entry: {
app: "./index.js",
},
target: "web",
output: {
path: path.resolve(__dirname, "dist"),
filename: "frontend.js",
libraryTarget: "var",
library: 'plugin',
},
// optimization: {
// minimize: false,
// },
};
module.exports = config;