/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <fstream>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <boost/thread.hpp>
#include <boost/algorithm/string/join.hpp>
#include "drill/drillc.hpp"

int nOptions=25;

struct Option{
    char name[32];
    char desc[128];
    bool required;
}qsOptions[]= {
    {"plan", "Plan files separated by semicolons", false},
    {"query", "Query strings, separated by semicolons", false},
    {"type", "Query type [physical|logical|sql|server]", true},
    {"connectStr", "Connect string", true},
    {"schema", "Default schema", false},
    {"api", "API type [sync|async|meta]", true},
    {"logLevel", "Logging level [trace|debug|info|warn|error|fatal]", false},
    {"testCancel", "Cancel the query after the first record batch.", false},
    {"syncSend", "Send query only after previous result is received", false},
    {"hshakeTimeout", "Handshake timeout (second).", false},
    {"queryTimeout", "Query timeout (second).", false},
    {"heartbeatFrequency", "Heartbeat frequency (second). Disabled if set to 0.", false},
    {"user", "Username", false},
    {"password", "Password", false},
    {"saslPluginPath", "Path to where SASL plugins are installed", false},
    {"service_host", "Service host for Kerberos", false},
    {"service_name", "Service name for Kerberos", false},
    {"auth", "Authentication mechanism to use", false},
    {"sasl_encrypt", "Negotiate for encrypted connection", false},
    {"enableSSL", "Enable SSL", false},
    {"TLSProtocol", "TLS protocol version", false},
    {"certFilePath", "Path to SSL certificate file", false},
    {"disableHostnameVerification", "disable host name verification", false},
    {"disableCertVerification", "disable certificate verification", false},
    {"useSystemTrustStore", "[Windows only]. Use the system truststore.", false },
    {"CustomSSLCtxOptions", "The custom SSL CTX Options", false}

};

std::map<std::string, std::string> qsOptionValues;

bool bTestCancel=false;
bool bSyncSend=false;


Drill::status_t SchemaListener(void* ctx, Drill::FieldDefPtr fields, Drill::DrillClientError* err){
    if(!err){
        std::cout<< "SCHEMA CHANGE DETECTED:" << std::endl;
        for(size_t i=0; i<fields->size(); i++){
            std::string name= fields->at(i)->getName();
            std::cout << name << "\t";
        }
        std::cout << std::endl;
        return Drill::QRY_SUCCESS ;
    }else{
        std::cerr<< "ERROR: " << err->msg << std::endl;
        return Drill::QRY_FAILURE;
    }
}

boost::mutex listenerMutex;
Drill::status_t QueryResultsListener(void* ctx, Drill::RecordBatch* b, Drill::DrillClientError* err){
    // Invariant:
    // (received an record batch and err is NULL)
    // or
    // (received query state message passed by `err` and b is NULL)
    boost::lock_guard<boost::mutex> listenerLock(listenerMutex);
    if(!err){
        if(b!=NULL){
            b->print(std::cout, 0); // print all rows
            std::cout << "DATA RECEIVED ..." << std::endl;
            delete b; // we're done with this batch, we can delete it
            if(bTestCancel){
                return Drill::QRY_FAILURE;
            }else{
                return Drill::QRY_SUCCESS ;
            }
        }else{
            std::cout << "Query Complete." << std::endl;
            return Drill::QRY_SUCCESS;
		}
    }else{
        assert(b==NULL);
        switch(err->status) {
            case Drill::QRY_COMPLETED:
            case Drill::QRY_CANCELED:
                std::cerr<< "INFO: " << err->msg << std::endl;
                return Drill::QRY_SUCCESS;
            default:
                std::cerr<< "ERROR: " << err->msg << std::endl;
                return Drill::QRY_FAILURE;
        }
    }
}

void print(const Drill::FieldMetadata* pFieldMetadata, void* buf, size_t sz){
    common::MinorType type = pFieldMetadata->getMinorType();
    common::DataMode mode = pFieldMetadata->getDataMode();
    unsigned char printBuffer[10240];
    memset(printBuffer, 0, sizeof(printBuffer));
    switch (type) {
        case common::BIGINT:
            switch (mode) {
                case common::DM_REQUIRED:
                    sprintf((char*)printBuffer, "%lld", *(uint64_t*)buf);
                    break;
                case common::DM_OPTIONAL:
                    break;
                case common::DM_REPEATED:
                    break;
            }
            break;
        case common::VARBINARY:
            switch (mode) {
                case common::DM_REQUIRED:
                    memcpy(printBuffer, buf, sz);
                    break;
                case common::DM_OPTIONAL:
                    break;
                case common::DM_REPEATED:
                    break;
            }
            break;
        case common::VARCHAR:
            switch (mode) {
                case common::DM_REQUIRED:
                    memcpy(printBuffer, buf, sz);
                    break;
                case common::DM_OPTIONAL:
                    break;
                case common::DM_REPEATED:
                    break;
            }
            break;
        default:
            //memcpy(printBuffer, buf, sz);
            sprintf((char*)printBuffer, "NIY");
            break;
    }
    printf("%s\t", (char*)printBuffer);
    return;
}

void printUsage(){
    std::cerr<<"Usage: querySubmitter ";
    for(int j=0; j<nOptions ;j++){
        std::cerr<< " "<< qsOptions[j].name <<"="  << "[" <<qsOptions[j].desc <<"]" ;
    }
    std::cerr<<std::endl;
}

int parseArgs(int argc, char* argv[]){
    bool error=false;
    for(int i=1; i<argc; i++){
        char*a =argv[i];
        char* o=strtok(a, "=");
        char*v=strtok(NULL, "");

        bool found=false;
        for(int j=0; j<nOptions ;j++){
            if(!strcmp(qsOptions[j].name, o)){
                found=true; break;
            }
        }
        if(!found){
            std::cerr<< "Unknown option:"<< o <<". Ignoring" << std::endl;
            continue;
        }

        if(v==NULL){
            std::cerr<< ""<< qsOptions[i].name << " [" <<qsOptions[i].desc <<"] " << "requires a parameter."  << std::endl;
            error=true;
        }
        qsOptionValues[o]=v;
    }

    for(int j=0; j<nOptions ;j++){
        if(qsOptions[j].required ){
            if(qsOptionValues.find(qsOptions[j].name) == qsOptionValues.end()){
                std::cerr<< ""<< qsOptions[j].name << " [" <<qsOptions[j].desc <<"] " << "is required." << std::endl;
                error=true;
            }
        }
    }
    if(error){
        printUsage();
        exit(1);
    }
    return 0;
}

void parseUrl(std::string& url, std::string& protocol, std::string& host, std::string& port){
    char u[1024];
    strcpy(u,url.c_str());
    char* z=strtok(u, "=");
    char* h=strtok(NULL, ":");
    char* p=strtok(NULL, ":");
    protocol=z; host=h; port=p;
}

std::vector<std::string> &splitString(const std::string& s, char delim, std::vector<std::string>& elems){
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, delim)){
        elems.push_back(item);
    }
    return elems;
}

int readPlans(const std::string& planList, std::vector<std::string>& plans){
    std::vector<std::string> planFiles;
    std::vector<std::string>::iterator iter;
    splitString(planList, ';', planFiles);
    for(iter = planFiles.begin(); iter != planFiles.end(); iter++) {
        std::ifstream f((*iter).c_str());
        std::string plan((std::istreambuf_iterator<char>(f)), (std::istreambuf_iterator<char>()));
        std::cout << "plan:" << plan << std::endl;
        plans.push_back(plan);
    }
    return 0;
}

int readQueries(const std::string& queryList, std::vector<std::string>& queries){
    splitString(queryList, ';', queries);
    return 0;
}

bool validate(const std::string& type, const std::string& query, const std::string& plan){
	if (type != "sync" || type != "async") {
		return true;
	}
    if(query.empty() && plan.empty()){
        std::cerr<< "Either query or plan must be specified"<<std::endl;
        return false;    }
        if(type=="physical" || type == "logical" ){
            if(plan.empty()){
                std::cerr<< "A logical or physical  plan must be specified"<<std::endl;
                return false;
            }
        }else
            if(type=="sql"){
                if(query.empty()){
                    std::cerr<< "A drill SQL query must be specified"<<std::endl;
                    return false;
                }
            }else{
                std::cerr<< "Unknown query type: "<< type << std::endl;
                return false;
            }
        return true;
}

Drill::logLevel_t getLogLevel(const char *s){
    if(s!=NULL){
        if(!strcmp(s, "trace")) return Drill::LOG_TRACE;
        if(!strcmp(s, "debug")) return Drill::LOG_DEBUG;
        if(!strcmp(s, "info")) return Drill::LOG_INFO;
        if(!strcmp(s, "warn")) return Drill::LOG_WARNING;
        if(!strcmp(s, "error")) return Drill::LOG_ERROR;
        if(!strcmp(s, "fatal")) return Drill::LOG_FATAL;
    }
    return Drill::LOG_ERROR;
}

int main(int argc, char* argv[]) {
    try {

        parseArgs(argc, argv);

        std::vector<std::string*> queries;

        std::string connectStr=qsOptionValues["connectStr"];
        std::string schema=qsOptionValues["schema"];
        std::string queryList=qsOptionValues["query"];
        std::string planList=qsOptionValues["plan"];
        std::string api=qsOptionValues["api"];
        std::string type_str=qsOptionValues["type"];
        std::string logLevel=qsOptionValues["logLevel"];
        std::string testCancel=qsOptionValues["testCancel"];
        std::string syncSend=qsOptionValues["syncSend"];
        std::string hshakeTimeout=qsOptionValues["hshakeTimeout"];
        std::string queryTimeout=qsOptionValues["queryTimeout"];
        std::string heartbeatFrequency=qsOptionValues["heartbeatFrequency"];
        std::string user=qsOptionValues["user"];
        std::string password=qsOptionValues["password"];
        std::string saslPluginPath=qsOptionValues["saslPluginPath"];
        std::string sasl_encrypt=qsOptionValues["sasl_encrypt"];
        std::string serviceHost=qsOptionValues["service_host"];
        std::string serviceName=qsOptionValues["service_name"];
        std::string auth=qsOptionValues["auth"];
        std::string enableSSL=qsOptionValues["enableSSL"];
        std::string tlsProtocol=qsOptionValues["TLSProtocol"];
        std::string certFilePath=qsOptionValues["certFilePath"];
        std::string disableHostnameVerification=qsOptionValues["disableHostnameVerification"];
        std::string disableCertVerification=qsOptionValues["disableCertVerification"];
        std::string useSystemTrustStore = qsOptionValues["useSystemTrustStore"];
        std::string customSSLOptions = qsOptionValues["CustomSSLCtxOptions"];

        Drill::QueryType type;

        if(!validate(type_str, queryList, planList)){
            exit(1);
        }

        Drill::logLevel_t l=getLogLevel(logLevel.c_str());

        std::vector<std::string> queryInputs;
        if(type_str=="sql" ){
            readQueries(queryList, queryInputs);
            type=Drill::SQL;
        }else if(type_str=="physical" ){
            readPlans(planList, queryInputs);
            type=Drill::PHYSICAL;
        }else if(type_str == "logical"){
            readPlans(planList, queryInputs);
            type=Drill::LOGICAL;
        }else{
            readQueries(queryList, queryInputs);
            type=Drill::SQL;
        }

        bTestCancel = !strcmp(testCancel.c_str(), "true")?true:false;
        bSyncSend = !strcmp(syncSend.c_str(), "true")?true:false;

        std::vector<std::string>::iterator queryInpIter;

        std::vector<Drill::RecordIterator*> recordIterators;
        std::vector<Drill::RecordIterator*>::iterator recordIterIter;

        std::vector<Drill::QueryHandle_t> queryHandles;
        std::vector<Drill::QueryHandle_t>::iterator queryHandleIter;

        Drill::DrillClient client;
#if defined _WIN32 || defined _WIN64
        TCHAR tempPath[MAX_PATH];
        GetTempPath(MAX_PATH, tempPath);
		char logpathPrefix[MAX_PATH + 128];
		strcpy(logpathPrefix,tempPath);
		strcat(logpathPrefix, "\\drillclient");
#else
		const char* logpathPrefix = "/var/log/drill/drillclient";
#endif
		// To log to file
        Drill::DrillClient::initLogging(logpathPrefix, l);
        // To log to stderr
        //Drill::DrillClient::initLogging(NULL, l);

        int nQueries=queryInputs.size();
        Drill::DrillClientConfig::setBufferLimit(nQueries*2*1024*1024); // 2MB per query. The size of a record batch may vary, but is unlikely to exceed the 256 MB which is the default. 

        if(!hshakeTimeout.empty()){
            Drill::DrillClientConfig::setHandshakeTimeout(atoi(hshakeTimeout.c_str()));
        }
        if (!queryTimeout.empty()){
            Drill::DrillClientConfig::setQueryTimeout(atoi(queryTimeout.c_str()));
        }
        if(!heartbeatFrequency.empty()) {
            Drill::DrillClientConfig::setHeartbeatFrequency(atoi(heartbeatFrequency.c_str()));
        }
        if (!saslPluginPath.empty()){
            Drill::DrillClientConfig::setSaslPluginPath(saslPluginPath.c_str());
        }

        Drill::DrillUserProperties props;
        if(schema.length()>0){
            props.setProperty(USERPROP_SCHEMA, schema);
        }
        if(user.length()>0){
            props.setProperty(USERPROP_USERNAME, user);
        }
        if(password.length()>0){
            props.setProperty(USERPROP_PASSWORD, password);
        }
        if(sasl_encrypt.length()>0){
            props.setProperty(USERPROP_SASL_ENCRYPT, sasl_encrypt);
        }
        if(serviceHost.length()>0){
            props.setProperty(USERPROP_SERVICE_HOST, serviceHost);
        }
        if(serviceName.length()>0){
            props.setProperty(USERPROP_SERVICE_NAME, serviceName);
        }
        if(auth.length()>0){
            props.setProperty(USERPROP_AUTH_MECHANISM, auth);
        }
        if(enableSSL.length()>0){
            props.setProperty(USERPROP_USESSL, enableSSL);
			if (enableSSL == "true" && certFilePath.length() <= 0 && useSystemTrustStore.length() <= 0){
                std::cerr<< "SSL is enabled but no certificate or truststore provided. " << std::endl;
                return -1;
            }
            props.setProperty(USERPROP_TLSPROTOCOL, tlsProtocol);
            props.setProperty(USERPROP_CERTFILEPATH, certFilePath);
            props.setProperty(USERPROP_DISABLE_HOSTVERIFICATION, disableHostnameVerification);
            props.setProperty(USERPROP_DISABLE_CERTVERIFICATION, disableCertVerification);
			if (useSystemTrustStore.length() > 0){
				props.setProperty(USERPROP_USESYSTEMTRUSTSTORE, useSystemTrustStore);
			}
            if (customSSLOptions.length() > 0){
                props.setProperty(USERPROP_CUSTOM_SSLCTXOPTIONS, customSSLOptions);
            }
        }

        if(client.connect(connectStr.c_str(), &props)!=Drill::CONN_SUCCESS){
            std::cerr<< "Failed to connect with error: "<< client.getError() << " (Using:"<<connectStr<<")"<<std::endl;
            return -1;
        }
        std::cout<< "Connected!\n" << std::endl;
        if(api=="meta") {
        	Drill::Metadata* metadata = client.getMetadata();
        	if (metadata) {
        		std::cout << "Connector:" << std::endl;
        		std::cout << "\tname:" << metadata->getConnectorName() << std::endl;
        		std::cout << "\tversion:" << metadata->getConnectorVersion() << std::endl;
        		std::cout << std::endl;
        		std::cout << "Server:" << std::endl;
        		std::cout << "\tname:" << metadata->getServerName() << std::endl;
        		std::cout << "\tversion:" << metadata->getServerVersion() << std::endl;
        		std::cout << std::endl;
        		std::cout << "Metadata:" << std::endl;
        		std::cout << "\tall tables are selectable: " << metadata->areAllTableSelectable() << std::endl;
        		std::cout << "\tcatalog separator: " << metadata->getCatalogSeparator() << std::endl;
        		std::cout << "\tcatalog term: " << metadata->getCatalogTerm() << std::endl;
        		std::cout << "\tCOLLATE support: " << metadata->getCollateSupport() << std::endl;
        		std::cout << "\tcorrelation names: " << metadata->getCorrelationNames() << std::endl;
        		std::cout << "\tdate time functions: " << boost::algorithm::join(metadata->getDateTimeFunctions(), ", ") << std::endl;
        		std::cout << "\tdate time literals support: " << metadata->getDateTimeLiteralsSupport() << std::endl;
        		std::cout << "\tGROUP BY support: " << metadata->getGroupBySupport() << std::endl;
        		std::cout << "\tidentifier case: " << metadata->getIdentifierCase() << std::endl;
        		std::cout << "\tidentifier quote string: " << metadata->getIdentifierQuoteString() << std::endl;
        		std::cout << "\tmax binary literal length: " << metadata->getMaxBinaryLiteralLength() << std::endl;
        		std::cout << "\tmax catalog name length: " << metadata->getMaxCatalogNameLength() << std::endl;
        		std::cout << "\tmax char literal length: " << metadata->getMaxCharLiteralLength() << std::endl;
        		std::cout << "\tmax column name length: " << metadata->getMaxColumnNameLength() << std::endl;
        		std::cout << "\tmax columns in GROUP BY: " << metadata->getMaxColumnsInGroupBy() << std::endl;
        		std::cout << "\tmax columns in ORDER BY: " << metadata->getMaxColumnsInOrderBy() << std::endl;
        		std::cout << "\tmax columns in SELECT: " << metadata->getMaxColumnsInSelect() << std::endl;
        		std::cout << "\tmax cursor name length: " << metadata->getMaxCursorNameLength() << std::endl;
        		std::cout << "\tmax logical lob size: " << metadata->getMaxLogicalLobSize() << std::endl;
        		std::cout << "\tmax row size: " << metadata->getMaxRowSize() << std::endl;
        		std::cout << "\tmax schema name length: " << metadata->getMaxSchemaNameLength() << std::endl;
        		std::cout << "\tmax statement length: " << metadata->getMaxStatementLength() << std::endl;
        		std::cout << "\tmax statements: " << metadata->getMaxStatements() << std::endl;
        		std::cout << "\tmax table name length: " << metadata->getMaxTableNameLength() << std::endl;
        		std::cout << "\tmax tables in SELECT: " << metadata->getMaxTablesInSelect() << std::endl;
        		std::cout << "\tmax user name length: " << metadata->getMaxUserNameLength() << std::endl;
        		std::cout << "\tNULL collation: " << metadata->getNullCollation() << std::endl;
        		std::cout << "\tnumeric functions: " << boost::algorithm::join(metadata->getNumericFunctions(), ", ") << std::endl;
        		std::cout << "\tOUTER JOIN support: " << metadata->getOuterJoinSupport() << std::endl;
        		std::cout << "\tquoted identifier case: " << metadata->getQuotedIdentifierCase() << std::endl;
        		std::cout << "\tSQL keywords: " << boost::algorithm::join(metadata->getSQLKeywords(), ",") << std::endl;
        		std::cout << "\tschema term: " << metadata->getSchemaTerm() << std::endl;
        		std::cout << "\tsearch escape string: " << metadata->getSearchEscapeString() << std::endl;
        		std::cout << "\tspecial characters: " << metadata->getSpecialCharacters() << std::endl;
        		std::cout << "\tstring functions: " << boost::algorithm::join(metadata->getStringFunctions(), ",") << std::endl;
        		std::cout << "\tsub query support: " << metadata->getSubQuerySupport() << std::endl;
        		std::cout << "\tsystem functions: " << boost::algorithm::join(metadata->getSystemFunctions(), ",") << std::endl;
        		std::cout << "\ttable term: " << metadata->getTableTerm() << std::endl;
        		std::cout << "\tUNION support: " << metadata->getUnionSupport() << std::endl;
        		std::cout << "\tBLOB included in max row size: " << metadata->isBlobIncludedInMaxRowSize() << std::endl;
        		std::cout << "\tcatalog at start: " << metadata->isCatalogAtStart() << std::endl;
        		std::cout << "\tcolumn aliasing supported: " << metadata->isColumnAliasingSupported() << std::endl;
        		std::cout << "\tLIKE escape clause supported: " << metadata->isLikeEscapeClauseSupported() << std::endl;
        		std::cout << "\tNULL plus non NULL equals to NULL: " << metadata->isNullPlusNonNullNull() << std::endl;
        		std::cout << "\tread-only: " << metadata->isReadOnly() << std::endl;
        		std::cout << "\tSELECT FOR UPDATE supported: " << metadata->isSelectForUpdateSupported() << std::endl;
        		std::cout << "\ttransaction supported: " << metadata->isTransactionSupported() << std::endl;
        		std::cout << "\tunrelated columns in ORDER BY supported: " << metadata->isUnrelatedColumnsInOrderBySupported() << std::endl;

        		client.freeMetadata(&metadata);
        	} else {
        		std::cerr << "Cannot get metadata:" << client.getError() << std::endl;
        	}
        } else if(api=="sync"){
            Drill::DrillClientError* err=NULL;
            Drill::status_t ret;
            int nQueries=0;
            for(queryInpIter = queryInputs.begin(); queryInpIter != queryInputs.end(); queryInpIter++) {
                Drill::RecordIterator* pRecIter = client.submitQuery(type, *queryInpIter, err);
                if(pRecIter!=NULL){
                    recordIterators.push_back(pRecIter);
                    nQueries++;
                }
            }
            Drill::DrillClientConfig::setBufferLimit(nQueries*2*1024*1024); // 2MB per query. Allows us to hold at least two record batches.
            size_t row=0;
            for(recordIterIter = recordIterators.begin(); recordIterIter != recordIterators.end(); recordIterIter++) {
                // get fields.
                row=0;
                Drill::RecordIterator* pRecIter=*recordIterIter;
                Drill::FieldDefPtr fields= pRecIter->getColDefs();
                while((ret=pRecIter->next()), (ret==Drill::QRY_SUCCESS || ret==Drill::QRY_SUCCESS_WITH_INFO) && !pRecIter->hasError()){
                    fields = pRecIter->getColDefs();
                    row++;
                    if( (ret==Drill::QRY_SUCCESS_WITH_INFO  && pRecIter->hasSchemaChanged() )|| ( row%100==1)){
                        for(size_t i=0; i<fields->size(); i++){
                            std::string name= fields->at(i)->getName();
                            printf("%s\t", name.c_str());
                        }
                        printf("\n");
                    }
                    printf("ROW: %ld\t", row);
                    for(size_t i=0; i<fields->size(); i++){
                        void* pBuf; size_t sz;
                        pRecIter->getCol(i, &pBuf, &sz);
                        print(fields->at(i), pBuf, sz);
                    }
                    printf("\n");
                    if(bTestCancel && row%100==1){
                        pRecIter->cancel();
                        printf("Application cancelled the query.\n");
                    }
                }
                if(ret!=Drill::QRY_NO_MORE_DATA && ret!=Drill::QRY_CANCEL){
                    std::cerr<< pRecIter->getError() << std::endl;
                }
                client.freeQueryIterator(&pRecIter);
            }
            client.waitForResults();
        }else{
            if(bSyncSend){
                for(queryInpIter = queryInputs.begin(); queryInpIter != queryInputs.end(); queryInpIter++) {
                    Drill::QueryHandle_t qryHandle;
                    client.submitQuery(type, *queryInpIter, QueryResultsListener, NULL, &qryHandle);
                    client.registerSchemaChangeListener(&qryHandle, SchemaListener);
                    
                     if(bTestCancel) {
                        // Send cancellation request after 5seconds
                        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
                        std::cout<< "\n Cancelling query: " << *queryInpIter << "\n" << std::endl;
                        client.cancelQuery(qryHandle);
                    } else {
                        client.waitForResults();
                    }

                    client.freeQueryResources(&qryHandle);
                }

            }else{
                for(queryInpIter = queryInputs.begin(); queryInpIter != queryInputs.end(); queryInpIter++) {
                    Drill::QueryHandle_t qryHandle;
                    client.submitQuery(type, *queryInpIter, QueryResultsListener, NULL, &qryHandle);
                    client.registerSchemaChangeListener(&qryHandle, SchemaListener);
                    queryHandles.push_back(qryHandle);
                }
                client.waitForResults();
                for(queryHandleIter = queryHandles.begin(); queryHandleIter != queryHandles.end(); queryHandleIter++) {
                    client.freeQueryResources(&*queryHandleIter);
                }
            }
        }
        client.close();
    } catch (std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

