UNPKG

sqlpad

Version:

Web app. Write SQL and visualize the results. Supports Postgres, MySQL, SQL Server, Crate, Vertica and SAP HANA.

249 lines (218 loc) 6.43 kB
const uuid = require('uuid'); const config = require('../lib/config'); const utils = require('./utils'); const getMeta = require('../lib/getMeta'); const debug = config.get('debug'); const drivers = {}; /** * Validate that the driver implementation has a function by name provided * @param {string} path * @param {object} driver * @param {string} functionName */ function validateFunction(path, driver, functionName) { if (typeof driver[functionName] !== 'function') { console.error(`${path} missing .${functionName}() implementation`); process.exit(1); } } /** * Validate that the driver implementation has an array by name provided * @param {string} path * @param {object} driver * @param {string} arrayName */ function validateArray(path, driver, arrayName) { const arr = driver[arrayName]; if (!Array.isArray(arr)) { console.error(`${path} missing ${arrayName} array`); process.exit(1); } } /** * Require driver implementation for provided path * and validate that it meets implementation spec as possible * @param {string} path */ function requireValidate(path, optional = false) { let driver; try { driver = require(path); } catch (er) { if (optional) { console.log('optional driver ' + path + ' not available'); return; } else { // rethrow throw er; } } if (!driver.id) { console.error(`${path} must export a unique id`); process.exit(1); } if (!driver.name) { console.error(`${path} must export a name`); process.exit(1); } if (drivers[driver.id]) { console.error(`Driver with id ${driver.id} already loaded`); console.error(`Ensure ${path} has a unique id exported`); process.exit(1); } validateFunction(path, driver, 'getSchema'); validateFunction(path, driver, 'runQuery'); validateFunction(path, driver, 'testConnection'); validateArray(path, driver, 'fields'); driver.fieldsByKey = {}; driver.fields.forEach(field => { driver.fieldsByKey[field.key] = field; }); drivers[driver.id] = driver; } // Loads and validates drivers // Will populate drivers {} map requireValidate('../drivers/crate'); requireValidate('../drivers/drill'); requireValidate('../drivers/hdb'); requireValidate('../drivers/mysql'); requireValidate('../drivers/postgres'); requireValidate('../drivers/presto'); requireValidate('../drivers/sqlserver'); requireValidate('../drivers/unixodbc', true); requireValidate('../drivers/vertica'); requireValidate('../drivers/cassandra'); if (debug || process.env.SQLPAD_TEST === 'true') { requireValidate('../drivers/mock'); } /** * Run query using driver implementation of connection * @param {*} query * @param {*} connection * @param {object} [user] user may not be provided if chart links turned on * @returns {Promise} */ function runQuery(query, connection, user) { const driver = drivers[connection.driver]; const queryResult = { id: uuid.v4(), cacheKey: null, startTime: new Date(), stopTime: null, queryRunTime: null, fields: [], incomplete: false, meta: {}, rows: [] }; return driver.runQuery(query, connection).then(results => { const { rows, incomplete } = results; if (!Array.isArray(rows)) { throw new Error(`${connection.driver}.runQuery() must return rows array`); } queryResult.incomplete = incomplete || false; queryResult.rows = rows; queryResult.stopTime = new Date(); queryResult.queryRunTime = queryResult.stopTime - queryResult.startTime; queryResult.meta = getMeta(rows); queryResult.fields = Object.keys(queryResult.meta); if (debug) { const connectionName = connection.name; const rowCount = rows.length; const { startTime, stopTime, queryRunTime } = queryResult; console.log( JSON.stringify({ userId: user && user._id, userEmail: user && user.email, connectionName, startTime, stopTime, queryRunTime, rowCount, query }) ); } return queryResult; }); } /** * Test connection passed in using the driver implementation * As long as promise resolves without error * it is considered a successful connection config * @param {object} connection */ function testConnection(connection) { const driver = drivers[connection.driver]; return driver.testConnection(connection); } /** * Gets schema (sometimes called schemaInfo) for connection * This data is used by client to build schema tree in editor sidebar * @param {object} connection * @returns {Promise} */ function getSchema(connection) { connection.maxRows = Number.MAX_SAFE_INTEGER; const driver = drivers[connection.driver]; return driver.getSchema(connection); } /** * Gets array of driver objects * @returns {array} drivers */ function getDrivers() { return Object.keys(drivers).map(id => { return { id, name: drivers[id].name, fields: drivers[id].fields }; }); } /** * Validates connection object based on its driver * Unnecessary fields will be stripped out * @param {object} connection */ function validateConnection(connection) { const coreFields = ['_id', 'name', 'driver', 'createdDate', 'modifiedDate']; if (!connection.name) { throw new Error('connection.name required'); } if (!connection.driver) { throw new Error('connection.driver required'); } const driver = drivers[connection.driver]; if (!driver) { throw new Error(`driver implementation ${connection.driver} not found`); } const validFields = driver.fields.map(field => field.key).concat(coreFields); const cleanedConnection = validFields.reduce( (cleanedConnection, fieldKey) => { if (connection.hasOwnProperty(fieldKey)) { let value = connection[fieldKey]; const fieldDefinition = drivers[connection.driver].fieldsByKey[fieldKey]; // field definition may not exist since // this could be a core field like _id, name if (fieldDefinition) { if (fieldDefinition.formType === 'CHECKBOX') { value = utils.ensureBoolean(value); } } cleanedConnection[fieldKey] = value; } return cleanedConnection; }, {} ); return cleanedConnection; } module.exports = { getDrivers, getSchema, runQuery, testConnection, validateConnection };