censql
Version:
A NodeJS command line client for SAP HANA
428 lines (340 loc) • 12.4 kB
JavaScript
var ScreenManager = require("./bin/ScreenManager.js");
var CommandHandler = require("./bin/CommandHandler.js");
var HDB = require("./lib/HdbHandler.js");
var SettingsHandler = require("./lib/SettingsHandler.js");
var argv = require('optimist').argv;
var mkdirp = require('mkdirp');
var path = require('path');
var async = require("async");
var osHomedir = require('os-homedir');
var fs = require("fs");
var SavedConnectionManager = require("./bin/SavedConnectionManager.js");
var CliTable = require('cli-table2');
var pkg = require("./package.json");
var username = require('username');
var CenSql = function() {
/**
* Bodge to fix hdb module
* @type {Boolean}
*/
process.browser = true;
/**
* get a global object for storing flags in
*/
global.censql = {};
/**
* Dont accept user input until we're ready
* @type {Boolean}
*/
global.censql.RUNNING_PROCESS = true;
/**
* Init hana library
* @type {HDB}
*/
this.hdb = new HDB();
async.series([
this.createFolderIfNeeded.bind(this),
this.loadSettings.bind(this),
this.showHelpTextIfNeeded.bind(this),
this.createScreen.bind(this),
function(callback) {
/**
* Create a new command handler
*/
this.commandHandler = new CommandHandler(this.screen, this.hdb, this.settings);
callback(null, null);
}.bind(this)
], function() {
this.connManager = new SavedConnectionManager();
/**
* Should version
*/
if (argv.version) {
this.showVersion();
return;
}
/**
* Should list connections
*/
if (argv.l || argv.list_connections) {
this.listConfiguredConnectionNames();
return;
}
/**
* Should check all connections
*/
if (argv.t || argv.connection_test) {
this.testSavedConnections();
return;
}
/**
* Should forget a connection
*/
if (argv.forget) {
this.connManager.delete(argv.forget);
console.log(("Forgot " + argv.forget + "!").green);
return;
}
var connDetails = argv;
if (argv.use) {
connDetails = this.connManager.get(argv.use);
}
if (connDetails) {
this.screen.init();
this.connectToHdb(connDetails.host, connDetails.user, connDetails.pass, connDetails.port, connDetails.tenant);
} else {
if (argv.use.length > 0) {
this.screen.print("Connection '".red + argv.use.red.bold + ("' does not exist. Use the \\save command whilst connected to save a connection.").red, function() {
console.log();
process.exit(1)
})
} else {
this.showHelp();
}
}
}.bind(this))
}
CenSql.prototype.loadSettings = function(callback){
/**
* Get settings
* @type {Object}
*/
this.settingsHandler = new SettingsHandler();
this.settingsHandler.getSettings(function(err, settings){
if(err){
callback(err);
return
}
this.settings = settings;
callback(null);
}.bind(this));
}
CenSql.prototype.testSavedConnections = function() {
var contents = this.connManager.getAll();
var keys = Object.keys(contents);
keys = keys.sort(function(a, b) {
if (a.toLowerCase() > b.toLowerCase()) return 1;
if (b.toLowerCase() > a.toLowerCase()) return -1;
return 0;
})
async.mapLimit(keys, 50, function(key, callback) {
var entry = contents[key];
var db = new HDB();
/**
* Connect to HANA with the login info supplied by the user
*/
db.connect(entry["host"], entry["user"], entry["pass"], entry["port"], entry["tenant"], "tmp_" + key, function(err, data) {
if (err) {
callback(null, {
status: 1,
key: key,
message: "Error connecting: " + err
});
return;
}
db.close();
callback(null, {
status: 0,
key: key,
message: "Connected Successfully"
});
}.bind(this))
}, function(err, output) {
if (err) {
console.log(err);
return;
}
output = output.sort(function(a, b) {
return a.key.localeCompare(b.key);
})
var table = new CliTable({
chars: (new(require("./lib/CharacterCodeIndex.js"))).tableChars
});
table.push(["Name".bold, "Status".bold, "Details".bold])
for (var i = 0; i < output.length; i++) {
table.push([output[i].key.bold, (output[i].status == 0 ? "OK".green.bold : "ERROR".red.bold), output[i].message])
}
console.log(table.toString());
process.exit(0);
})
}
CenSql.prototype.showVersion = function() {
console.log(pkg.version)
process.exit(0);
}
CenSql.prototype.listConfiguredConnectionNames = function() {
var contents = this.connManager.getAll();
var names = Object.keys(contents);
names = names.sort(function(a, b) {
if (a.toLowerCase() > b.toLowerCase()) return 1;
if (b.toLowerCase() > a.toLowerCase()) return -1;
return 0;
})
var table = new CliTable({
chars: (new(require("./lib/CharacterCodeIndex.js"))).tableChars
});
table.push(["Name".bold, "User".bold, "Host".bold, "Port".bold, "Tenant".bold]);
for (var i = 0; i < names.length; i++) {
table.push([names[i].bold, contents[names[i]].user, contents[names[i]].host, contents[names[i]].port, contents[names[i]].tenant || "N/A"])
}
console.log(table.toString());
}
CenSql.prototype.setTitle = function(isStudio) {
var title = "CenSQL" + (isStudio ? " Studio" : "");
if (argv.use) title += ' - ' + argv.use;
process.stdout.write(String.fromCharCode(27) + "]0;" + title + String.fromCharCode(7));
process.title = title;
}
CenSql.prototype.connectToHdb = function(host, user, pass, port, tenant) {
/**
* Connect to HANA with the login info supplied by the user
*/
this.hdb.connect(host, user, pass, port, tenant, "conn", function(err, data) {
if (err) {
this.screen.error(err.message + "\n", function() {
process.exit(1);
});
return;
}
this.setConnectionMetaData("conn", function(err) {
if (err) {
this.screen.error(err.message + "\n", function() {
process.exit(1);
});
return;
}
/**
* If the user specified the command run it, otherwise, open an interactive session
*/
if (!argv.command) {
this.startCLI(user);
} else {
this.runBatchCommand();
}
}.bind(this))
}.bind(this))
}
CenSql.prototype.startCLI = function(user) {
this.hdb.exec("conn", "SELECT (SELECT SYSTEM_ID FROM SYS.M_DATABASE) AS SYSTEM_ID, (SELECT DATABASE_NAME FROM SYS.M_DATABASE) AS DATABASE_NAME, (SELECT USAGE FROM SYS.M_DATABASE) AS USAGE, CURRENT_SCHEMA FROM DUMMY", function(err, data) {
/**
* Allow user inpput from now on
* @type {Boolean}
*/
global.censql.RUNNING_PROCESS = false;
if (err) {
this.screen.ready(this.hdb, user, null, null, null);
return;
} else {
this.screen.ready(this.hdb, user, data[0].SYSTEM_ID, data[0].DATABASE_NAME, data[0].USAGE, data[0].CURRENT_SCHEMA);
}
}.bind(this))
}
CenSql.prototype.runBatchCommand = function() {
this.screen.readyBatch();
var command = argv.command;
/**
* Odd input can occur when running from some terminals
*/
if(typeof command !== "string"){
command = "";
}
this.commandHandler.onCommand(command, function(err, output) {
this.screen.printCommandOutput(command, output, function() {
process.exit(0)
});
}.bind(this));
}
CenSql.prototype.setConnectionMetaData = function(connId, callback) {
async.parallel([
function(callback) {
this.hdb.exec("conn", "SET 'APPLICATION' = 'CenSQL'", callback)
}.bind(this),
function(callback) {
this.hdb.exec("conn", "SET 'APPLICATIONVERSION' = '" + pkg.version + "'", callback)
}.bind(this),
function(callback) {
this.hdb.exec("conn", "SET 'APPLICATIONUSER' = '" + username.sync() + "'", callback)
}.bind(this)
], function(err, res) {
callback(err, null);
})
}
CenSql.prototype.createScreen = function(callback) {
/**
* keep the force_nocolour var in settings for easy access
* @type {boolean}
*/
this.settings.force_nocolour = argv['no-colour'] || argv['no-color'] ? true : false;
this.settings.studio = !!argv['studio'] || !!argv['s'];
/**
* Create screen object adn give it the command handler to handle user input
*/
this.screen = new ScreenManager(
// Command if given
!!argv.command,
this.settings,
// Command Handler
{
handleCommand: function(command, callback) {
this.commandHandler.onCommand(command, callback);
}.bind(this),
getActiveSchema: function(callback) {
this.commandHandler.getActiveSchema(callback);
}.bind(this),
getHandlers: function() {
return this.commandHandler.handlers
}.bind(this)
}
)
if (!argv.command) {
this.setTitle(this.settings.studio);
}
callback.call(this, null);
}
CenSql.prototype.showHelpTextIfNeeded = function(callback) {
/**
* Make sure we have the arguments needed to connect to HANA
*/
if (((!argv.host || !argv.user || !argv.pass || !argv.port) && (!argv.use || argv.use.length == 0) && !(argv.l || argv.list_connections) && !(argv.t || argv.connection_test) && !argv.forget && !argv.version) || argv.help) {
this.showHelp();
}
callback.call(this, null);
}
CenSql.prototype.showHelp = function() {
console.log([
"Usage:\t censql --user <USER> --port 3<ID>15 --host <IP OR HOSTNAME> --pass <PASSWORD>",
"\t censql --user <USER> --port 3<ID>15 --host <IP OR HOSTNAME> --pass <PASSWORD> --command '<SQL_STRING>'",
"\t censql --use <ALIAS>",
"Example: censql --user SYSTEM --port 30015 --host 192.168.0.1 --pass Password123",
"Example: censql --user SYSTEM --port 30015 --host 192.168.0.1 --pass Password123 --command 'SELECT * FROM SYS.M_SERVICES'",
"Example: censql --use DEV1",
"",
"CenSQL Help",
"--user\t\tThe username for the user to connect as",
"--pass\t\tThe password for the user connecting with",
"--host\t\tThe host to connect to",
"--port\t\tThe port to connect to the host with (Layout: '3<ID>15', Instance 99 would be 39915)",
"--tenant\t\tName of the tenant database if on a multitenant system",
"--use <ALIAS>\tConnect using a saved connection instead of user,pass,host,port",
"",
"--command\t\tOptionally run a command/sql without entering the interective terminal",
"-s --studio\t\tEnter studio mode",
"",
"-l --list_connections\tList saved connections",
"-t --connection_test\tConnect to every saved connection and check HANA's status",
"--forget <NAME>\t\tForget a saved connection",
"",
"--no-colour\t\tDisable colour output",
"--no-color\t\tAlias of --no-colour",
"",
"--version\tShow CenSQL version",
].join("\n"));
process.exit(0);
}
CenSql.prototype.createFolderIfNeeded = function(callback) {
var location = path.join(osHomedir(), ".censql");
mkdirp.sync(location, 0777);
callback(null);
};
new CenSql();