sqlpad
Version:
Web app. Write SQL and visualize the results. Supports Postgres, MySQL, SQL Server, Crate, Vertica and SAP HANA.
172 lines (157 loc) • 3.91 kB
JavaScript
const mssql = require('mssql');
const { formatSchemaQueryResults } = require('../utils');
const id = 'sqlserver';
const name = 'SQL Server';
const SCHEMA_SQL = `
SELECT
t.table_schema,
t.table_name,
c.column_name,
c.data_type
FROM
INFORMATION_SCHEMA.TABLES t
JOIN INFORMATION_SCHEMA.COLUMNS c ON t.table_schema = c.table_schema AND t.table_name = c.table_name
WHERE
t.table_schema NOT IN ('information_schema')
ORDER BY
t.table_schema,
t.table_name,
c.ordinal_position
`;
/**
* Run query for connection
* Should return { rows, incomplete }
* @param {string} query
* @param {object} connection
*/
function runQuery(query, connection) {
const config = {
user: connection.username,
password: connection.password,
server: connection.host,
port: connection.port ? connection.port : 1433,
database: connection.database,
domain: connection.domain,
// Set timeout to 1 hour for long running query support
requestTimeout: 1000 * 60 * 60,
options: {
appName: 'SQLPad',
encrypt: connection.sqlserverEncrypt
},
pool: {
max: 1,
min: 0,
idleTimeoutMillis: 1000
}
};
let incomplete;
const rows = [];
return new Promise((resolve, reject) => {
const pool = new mssql.ConnectionPool(config, err => {
if (err) {
return reject(err);
}
const request = new mssql.Request(pool);
// Stream set a config level doesn't seem to work
request.stream = true;
request.query(query);
request.on('row', row => {
// Special handling if columns were not given names
if (row[''] && row[''].length) {
for (let i = 0; i < row[''].length; i++) {
row['UNNAMED COLUMN ' + (i + 1)] = row[''][i];
}
delete row[''];
}
if (rows.length < connection.maxRows) {
return rows.push(row);
}
// If reached it means we received a row event for more than maxRows
// If we haven't flagged incomplete yet, flag it,
// Resolve what we have and cancel request
// Note that this will yield a cancel error
if (!incomplete) {
incomplete = true;
resolve({ rows, incomplete });
request.cancel();
}
});
// Error events may fire multiple times
// If we get an ECANCEL error and too many rows were handled it was intentional
request.on('error', err => {
if (err.code === 'ECANCEL' && incomplete) {
return;
}
return reject(err);
});
// Always emitted as the last one
request.on('done', () => {
resolve({ rows, incomplete });
pool.close();
});
});
pool.on('error', err => reject(err));
});
}
/**
* Test connectivity of connection
* @param {*} connection
*/
function testConnection(connection) {
const query = "SELECT 'success' AS TestQuery;";
return runQuery(query, connection);
}
/**
* Get schema for connection
* @param {*} connection
*/
function getSchema(connection) {
return runQuery(SCHEMA_SQL, connection).then(queryResult =>
formatSchemaQueryResults(queryResult)
);
}
const fields = [
{
key: 'host',
formType: 'TEXT',
label: 'Host/Server/IP Address'
},
{
key: 'port',
formType: 'TEXT',
label: 'Port (optional)'
},
{
key: 'database',
formType: 'TEXT',
label: 'Database'
},
{
key: 'username',
formType: 'TEXT',
label: 'Database Username'
},
{
key: 'password',
formType: 'PASSWORD',
label: 'Database Password'
},
{
key: 'domain',
formType: 'TEXT',
label: 'Domain'
},
{
key: 'sqlserverEncrypt',
formType: 'CHECKBOX',
label: 'Encrypt (necessary for Azure)'
}
];
module.exports = {
id,
name,
fields,
getSchema,
runQuery,
testConnection
};