hs2-thrift
Version:
Hive Server 2 client using Apache Thrift RPC able to query Impala for Javascript.
410 lines (387 loc) • 11.9 kB
JavaScript
/*
* @author Adrian Mroczek
* @version 1.0.10
* @github https://github.com/amroczeK/hs2-thrift
* @npm https://www.npmjs.com/package/hs2-thrift
*/
;
const thrift = require("thrift"),
service = require("../lib/gen-nodejs/TCLIService"),
serviceTypes = require("../lib/gen-nodejs/TCLIService_types"),
bunyan = require("bunyan");
var connection, client;
const transport = thrift.TBufferedTransport;
const protocol = thrift.TBinaryProtocol;
/* Create bunyan logger for logging */
const logger = bunyan.createLogger({
name: "HiveServer2ThriftClient",
streams: [
{
level: "info",
path: "./hive-thrift-logs.log",
},
{
level: "error",
path: "./hive-thrift-logs.log",
},
],
});
class HiveServer2ThriftClient {
constructor() {}
/*
* Connect to database and create thrift client
* @param config - the server configuration
* @return a promise with session or error
*/
connect(config) {
return new Promise((resolve, reject) => {
establishConnection(config)
.then((session) => {
resolve(session);
})
.catch((error) => {
reject(error);
});
});
}
/*
* Disconnect from the server and close thrift session
* @param session - the current thrift session
* @return a promise with response or error
*/
disconnect(session) {
return new Promise((resolve, reject) => {
closeConnection(session)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(error);
});
});
}
/*
* Execute a select statement
* @param session - the current thrift session
* @param statement - SQL statement/query
* @return a promise with result or error
*/
query(session, statement) {
return new Promise((resolve, reject) => {
executeQuery(session, statement)
.then((result) => {
resolve(result);
})
.catch((error) => {
reject(error);
});
});
}
/*
* Create a connection, session and execute SQL query
* @param session - the current thrift session
* @param statement - SQL statement/query
* @return a promise with response or error
*/
connectAndQuery(config, statement) {
logger.info("Sending SQL Query : " + statement);
return new Promise((resolve, reject) => {
establishConnection(config)
.then((session) => {
executeQuery(session, statement)
.then((result) => {
switch (config.retain_session) {
case true:
logger.info("Connection and session remains alive.");
break;
default:
closeConnection(session)
.then(() => {
resolve(result);
})
.catch((error) => {
reject(error);
});
}
})
.catch((error) => {
closeConnection(session)
.then(() => {
reject(error);
})
.catch((error) => {
reject(error);
});
});
})
.catch((error) => {
reject(error);
});
});
}
}
/*
* Connect to database and create thrift client
* @param config - the server configuration
* @return a promise with session or error
*/
const establishConnection = (config) => {
return new Promise((resolve, reject) => {
/* Create a connection and thrift client */
connection = thrift.createConnection(config.host, config.port, {
transport: transport,
protocol: protocol,
});
client = thrift.createClient(service, connection);
/* Handle connection errors */
connection.on("error", function (error) {
logger.error("Failed to make a thrift connection : " + JSON.stringify(error));
if (error) {
reject(error);
}
});
/* Handle connection success */
connection.on("connect", function () {
logger.info("Connection initialised for " + config.host + ":" + config.port);
openSessionThrift(config)
.then((response) => {
logger.info("Session opened successfully.");
resolve(response.sessionHandle);
})
.catch((error) => {
logger.error("Failed to make a connection with server : " + JSON.stringify(error));
reject(error);
});
});
});
};
/*
* Close connection to the server and close thrift session
* @param session - the current thrift session
* @return a promise with response or error
*/
const closeConnection = (session) => {
return new Promise((resolve, reject) => {
closeSessionThrift(session)
.then((response) => {
logger.info("Thrift session closed successfully.");
connection.on("end", function (error) {
if (error) {
logger.error("Disconnecting connection encountered an error : " + JSON.stringify(error));
reject(error);
}
});
connection.end();
logger.info("Connection disconnected successfully.");
resolve(response);
})
.catch((error) => {
logger.error("Failed to close thrift session : " + JSON.stringify(error));
reject(error);
});
});
};
/*
* Execute query against database
* @param session - the current thrift session
* @param statement - SQL statement/query
* @return a promise with result or error
*/
const executeQuery = (session, statement) => {
return new Promise((resolve, reject) => {
logger.info("Executing SQL Query : " + statement);
executeStatementThrift(session, statement)
.then((response) => {
getResults(response.operationHandle)
.then((result) => {
resolve(result);
})
.catch((error) => {
reject(error);
});
})
.catch((error) => {
logger.error("Failed to execute statement with error : " + JSON.stringify(error));
reject(error);
});
});
};
/*
* Open Hive/Thrift session
* @param config - the server configuration
* @return a promise with response or error
*/
const openSessionThrift = (config) => {
return new Promise((resolve, reject) => {
var protocol = setProtocolVersion(config);
var openSessReq = new serviceTypes.TOpenSessionReq();
openSessReq.username = config.username;
openSessReq.password = config.password;
openSessReq.client_protocol = protocol;
client.OpenSession(openSessReq, function (error, response) {
if (!error) {
resolve(response);
} else {
reject(error);
}
});
});
};
/*
* Set protocol to appropriate HiveServer2 Protocol Version
* @param config - the server configuration
* @return protocol for HiveServer2 Thrift Service version
*/
const setProtocolVersion = (config) => {
var protocol;
switch (config.protocol_ver) {
case 1:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V1;
break;
case 2:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V2;
break;
case 3:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V3;
break;
case 4:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V4;
break;
case 5:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V5;
break;
case 6:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V6;
break;
case 7:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V7;
break;
case 8:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V8;
break;
case 9:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V9;
break;
case 10:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V10;
break;
case 11:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V11;
break;
default:
protocol = serviceTypes.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V5;
}
return protocol;
};
/*
* Close Hive/Thrift session
* @param session - the current thrift session
* @return a promise with response or error
*/
const closeSessionThrift = (session) => {
return new Promise((resolve, reject) => {
var closeSessReq = new serviceTypes.TCloseSessionReq();
closeSessReq.sessionHandle = session;
client.CloseSession(closeSessReq, function (error, response) {
if (!error) {
resolve(response);
} else {
reject(error);
}
});
});
};
/*
* Execute SQL Statement
* @param session - the current thrift session
* @param statement - SQL statement/query
* @return a promise with response or error
*/
const executeStatementThrift = (session, statement) => {
return new Promise((resolve, reject) => {
var request = new serviceTypes.TExecuteStatementReq();
request.sessionHandle = session;
request.statement = statement;
request.runAsync = false;
client.ExecuteStatement(request, function (error, response) {
if (!error) {
resolve(response);
} else {
reject(error);
}
});
});
};
/*
* Execute GetResultSetMetadata
* @param operation - the current operation
* @return a promise with response or error
*/
const getResultSetMetadataThrift = (operation) => {
return new Promise((resolve, reject) => {
var request = new serviceTypes.TGetResultSetMetadataReq();
request.operationHandle = operation;
client.GetResultSetMetadata(request, function (error, response) {
if (!error) {
resolve(response);
} else {
reject(error);
}
});
});
};
/*
* Execute FetchResults action on operation result
* @param operation - the current operation
* @return a promise with response or error
*/
const fetchResultsThrift = (operation) => {
return new Promise((resolve, reject) => {
var request = new serviceTypes.TFetchResultsReq();
request.operationHandle = operation;
request.orientation = serviceTypes.TFetchOrientation.FETCH_NEXT;
request.maxRows = 2000; // Hardcoded maximum number of rows able to be returned
client.FetchResults(request, function (error, response) {
if (!error) {
resolve(response);
} else {
reject(error);
}
});
});
};
/*
* Retrieve and parse the response
* @param operation - the current operation
* @return a promise with response (result) or error
*/
const getResults = (operation) => {
logger.info("Getting results from SQL query.");
return new Promise((resolve, reject) => {
getResultSetMetadataThrift(operation)
.then((responseMeta) => {
logger.info("Get ResultSet Metadata Response = " + JSON.stringify(responseMeta));
fetchResultsThrift(operation)
.then((responseFetch) => {
logger.info("Fetch Results Response = " + JSON.stringify(responseFetch));
var columnNames = responseMeta.schema.columns.map((column) => column.columnName);
/* Format result returning columns and rows */
const result = responseFetch.results.rows.map((row) =>
row.colVals.map((col, c) => ({
[columnNames[c]]: Object.values(col).filter((val) => val)[0].value,
}))
);
resolve(result);
})
.catch((error) => {
logger.error("\nError fetching results : " + error);
reject(error);
});
})
.catch((error) => {
logger.error("\nError getting ResultSetMetadata : " + error);
reject(error);
});
});
};
module.exports = new HiveServer2ThriftClient();