Package edu.brown.terminal

Source Code of edu.brown.terminal.HStoreTerminal$TerminalConnection

package edu.brown.terminal;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jline.ConsoleReader;

import org.apache.log4j.Logger;
import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.catalog.Catalog;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.ProcParameter;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Site;
import org.voltdb.client.Client;
import org.voltdb.client.ClientFactory;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.NoConnectionsException;
import org.voltdb.types.TimestampType;
import org.voltdb.utils.NotImplementedException;
import org.voltdb.utils.VoltTableUtil;
import org.voltdb.utils.VoltTypeUtil;

import edu.brown.catalog.CatalogUtil;
import edu.brown.hstore.HStoreConstants;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.utils.ArgumentsParser;
import edu.brown.utils.CollectionUtil;
import edu.brown.utils.StringUtil;

/**
* H-Store Commandline Client Terminal
* @author gen
* @author pavlo
*/
public class HStoreTerminal implements Runnable {
    private static final Logger LOG = Logger.getLogger(HStoreTerminal.class);
    private static final LoggerBoolean debug = new LoggerBoolean();
   
    /**
     * Special non-standard commands that we can execute
     * These are to help us mimic MySQL
     */
    public enum Command {
        DESCRIBE("Not Implemented"),
        EXEC("ProcedureName [OptionalParams]"),
        ENABLE("OptionName"),
        SHOW("Not Implemented"),
        QUIT("");
       
        private final String usage;
        private Command(String usage) {
            this.usage = usage;
        }
    };
   
    private class TerminalConnection {
        final Client client;
        final String hostname;
        final int port;
       
        public TerminalConnection(Client client, String hostname, int port) {
            this.client = client;
            this.hostname = hostname;
            this.port = port;
        }
    } // CLASS
   
    // ---------------------------------------------------------------
    // STATIC CONFIGURATION MEMBERS
    // ---------------------------------------------------------------
   
    private static final String setPlainText = StringUtil.SET_PLAIN_TEXT;
    private static final String setBoldGreenText = "\033[1;32m"; // 0;1m";
    private static final String setBoldText = "\033[0;1m";

    private static final String PROMPT = setBoldGreenText + "hstore>" + setPlainText + " ";
    private static final Pattern SPLITTER = Pattern.compile("[ ]+");
   
    // ---------------------------------------------------------------
    // INSTANCE CONFIGURATION MEMBERS
    // ---------------------------------------------------------------
   
    private final Catalog catalog;
    private final Database catalog_db;
    private final jline.ConsoleReader reader = new ConsoleReader();
    private final TokenCompletor completer;
   
    // OPTIONS
    private boolean enable_csv = false;
    private boolean enable_debug = false;
    private String hostname = null;
    private int port = HStoreConstants.DEFAULT_PORT;
   
    // ---------------------------------------------------------------
    // CONSTRUCTOR
    // ---------------------------------------------------------------
   
    public HStoreTerminal(Catalog catalog) throws Exception{
        this.catalog = catalog;
        this.catalog_db = CatalogUtil.getDatabase(this.catalog);
       
        if (debug.val) LOG.debug("Generating tab-completion keywords");
        this.completer = new TokenCompletor(catalog);
        this.reader.addCompletor(this.completer);
    }
   
   
    @Override
    public void run() {
        TerminalConnection tc = this.getClientConnection();
        if (this.enable_csv == false) {
            this.printHeader();
            System.out.printf("Connected to %s:%d / Server Version: %s\n",
                              tc.hostname, tc.port, tc.client.getBuildString());
        }
       
        String query = "";
        ClientResponse cresponse = null;
        boolean stop = false;
        try {
            do {
                try {
                    query = (this.enable_csv ? reader.readLine() : reader.readLine(PROMPT));
                    if (query == null || query.isEmpty()) continue;
                    query = query.trim();
                   
                    // Check if the first token is one of our special keywords
                    String tokens[] = SPLITTER.split(query);
                   
                    int retries = 3;
                    Command targetCmd = null;
                    boolean usage = false;
                    boolean reconnect = false;
                    while (retries-- > 0 && stop == false) {
                        // Check whether they want to execute a special command
                        for (Command c : Command.values()) {
                            if (tokens[0].equalsIgnoreCase(c.name())) {
                                targetCmd = c;
                            }
                        } // FOR
                       
                        try {
                            if (targetCmd != null) {
                                switch (targetCmd) {
                                    case EXEC:
                                        // The second position should be the name of the procedure
                                        // that they want to execute
                                        if (tokens.length < 2) {
                                            usage = true;
                                        } else {
                                            cresponse = this.execProcedure(tc.client, tokens[1], query, reconnect);
                                        }
                                        break;
                                    case ENABLE:
                                        this.processEnable(tc.client, tokens[1], query, reconnect);
                                        break;
                                    case QUIT:
                                        stop = true;
                                        break;
                                    case DESCRIBE:
                                    case SHOW:
                                        throw new NotImplementedException("The command '" + targetCmd + "' is is not implemented");
                                    default:
                                        throw new RuntimeException("Unexpected command '" + targetCmd);
                                } // SWITCH
                            }
                            // Otherwise we'll send it to the server to deal with as an ad-hoc query
                            else {
                                cresponse = this.execQuery(tc.client, query);
                            }
                        } catch (NoConnectionsException ex) {
                            LOG.warn("Connection lost. Going to try to connect again...");
                            tc = this.getClientConnection();
                            reconnect = true;
                            continue;
                        }
                        break;
                    } // WHILE
                   
                    // Just print out the result
                    if (cresponse != null) {
                        if (cresponse.getStatus() == Status.OK) {
                            System.out.println(this.formatResult(cresponse));   
                        } else {
                            System.out.printf("Server Response: %s / %s\n",
                                              cresponse.getStatus(),
                                              cresponse.getStatusString());
                        }
                    }
                    // Print target command usage
                    else if (usage) {
                        assert(targetCmd != null);
                        System.out.print(setBoldText);
                        System.out.println("USAGE: " + targetCmd.name() + " " + targetCmd.usage);
                        System.out.print(setPlainText);
                    }
                    // Print warning if we're not supposed to stop
                    else if (stop == false && targetCmd != Command.ENABLE) {
                        LOG.warn("Return result is null");
                    }
                   
                // Fatal Error
                } catch (RuntimeException ex) {
                    throw ex;
                // Friendly Error
                } catch (Exception ex) {
                    LOG.error(ex.getMessage());
                    Throwable cause = ex.getCause();
                    if (cause != null) {
                        LOG.error(cause.getMessage());
                        if (debug.val) cause.printStackTrace();
                    }
                }
            } while (query != null && stop == false);
        } finally {
            try {
                if (tc != null) tc.client.close();
            } catch (InterruptedException ex) {
                // Ignore
            }
        }
    }
   
    private void printHeader() {
//        System.out.print(setBoldText);
        System.out.println(" _  _     ___ _____ ___  ___ ___");
        System.out.println("| || |___/ __|_   _/ _ \\| _ \\ __|");
        System.out.println("| __ |___\\__ \\ | || (_) |   / _|");
        System.out.println("|_||_|   |___/ |_| \\___/|_|_\\___|");
        System.out.println();
//        System.out.println(setPlainText);
    }
   
    /**
     * Get a client handle to a random site in the running cluster
     * The return value includes what site the client connected to
     * @return
     */
    private TerminalConnection getClientConnection() {
        String hostname = null;
        int port = -1;
       
        // Fixed hostname
        if (this.hostname != null) {
            if (this.hostname.contains(":")) {
                String split[] = this.hostname.split("\\:", 2);
                hostname = split[0];
                port = Integer.valueOf(split[1]);
            } else {
                hostname = this.hostname;
                port = this.port;
            }
        }
        // Connect to random host and using a random port that it's listening on
        else if (this.catalog != null) {
            Site catalog_site = CollectionUtil.random(CatalogUtil.getAllSites(this.catalog));
            hostname = catalog_site.getHost().getIpaddr();
            port = catalog_site.getProc_port();
        }
        assert(hostname != null);
        assert(port > 0);
       
        if (debug.val)
            LOG.debug(String.format("Creating new client connection to %s:%d",
                      hostname, port));
       
        Client client = ClientFactory.createClient(128, null, false, null);
        try {
            client.createConnection(null, hostname, port, "user", "password");
        } catch (Exception ex) {
            String msg = String.format("Failed to connect to HStoreSite at %s:%d", hostname, port);
            throw new RuntimeException(msg);
        }
        return new TerminalConnection(client, hostname, port);
    }
   
    /**
     * Execute the given query as an ad-hoc request on the server and
     * return the result.
     * @param client
     * @param query
     * @return
     * @throws Exception
     */
    private ClientResponse execQuery(Client client, String query) throws Exception {
        if (debug.val) LOG.debug("QUERY: " + query);
        ClientResponse cresponse = client.callProcedure("@AdHoc", query);
        return (cresponse);
    }
   
    /**
     * Execute the given procedure on the server and return the result
     * @param client
     * @param procName
     * @param query
     * @return
     * @throws Exception
     */
    private ClientResponse execProcedure(Client client, String procName, String query, boolean reconnect) throws Exception {
        Procedure catalog_proc = this.catalog_db.getProcedures().getIgnoreCase(procName);
        if (catalog_proc == null) {
            throw new Exception("Invalid stored procedure name '" + procName + "'");
        }
       
        // We now need to go through the rest of the parameters and convert them
        // to proper type
        Pattern p = Pattern.compile("^" + Command.EXEC.name() + "[ ]+" + procName + "[ ]+(.*?)[;]*", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(query);
        List<Object> procParams = new ArrayList<Object>();
        if (m.matches()) {
            // Extract the parameters and then convert them to their appropriate type
            List<String> params = HStoreTerminal.extractParams(m.group(1));
            if (debug.val) LOG.debug("PARAMS: " + params);
            if (params.size() != catalog_proc.getParameters().size()) {
                String msg = String.format("Expected %d params for '%s' but %d parameters were given",
                                           catalog_proc.getParameters().size(), catalog_proc.getName(), params.size());
                throw new Exception(msg);
            }
            int i = 0;
            for (ProcParameter catalog_param : catalog_proc.getParameters()) {
                VoltType vtype = VoltType.get(catalog_param.getType());
                Object value = VoltTypeUtil.getObjectFromString(vtype, params.get(i));
               
                // HACK: Allow us to send one-element array parameters
                if (catalog_param.getIsarray()) {
                    switch (vtype) {
                        case BOOLEAN:
                            value = new boolean[]{ (Boolean)value };
                            break;
                        case TINYINT:
                        case SMALLINT:
                        case INTEGER:
                            value = new int[]{ (Integer)value };
                            break;
                        case BIGINT:
                            value = new long[]{ (Long)value };
                            break;
                        case FLOAT:
                        case DECIMAL:
                            value = new double[]{ (Double)value };
                            break;
                        case STRING:
                            value = new String[]{ (String)value };
                            break;
                        case TIMESTAMP:
                            value = new TimestampType[]{ (TimestampType)value };
                        default:
                            assert(false);
                    } // SWITCH
                }
                procParams.add(value);
                i++;
            } // FOR
        }
       
        Object params[] = procParams.toArray();
        if (this.enable_csv == false && reconnect == false) {
            LOG.info(String.format("Executing transaction " + setBoldText + "%s(%s)" + setPlainText,
                     catalog_proc.getName(), StringUtil.toString(params, false, false)));
        }
        ClientResponse cresponse = client.callProcedure(catalog_proc.getName(), params);
        return (cresponse);
    }
   
    protected void processEnable(Client client, String option, String query, boolean reconnect) throws Exception {
        // HACK
        this.enable_debug = true;
        LOG.info("Enabled debug output");
    }
   
    /**
     *
     * @param paramStr
     * @return
     * @throws Exception
     */
    protected static List<String> extractParams(String paramStr) throws Exception {
        List<String> params = new ArrayList<String>();
        int pos = -1;
        int len = paramStr.length();
        while (++pos < len) {
            char cur = paramStr.charAt(pos);
           
            // Skip if it's just a space
            if (cur == ' ') continue;
           
            // See if our current position is a quotation mark
            // If it is, then we know that we have a string parameter
            if (cur == '"') {
                // Keep going until we reach an unescaped quotation mark
                boolean escaped = false;
                boolean valid = false;
                StringBuilder sb = new StringBuilder();
                while (++pos < len) {
                    cur = paramStr.charAt(pos);
                    if (cur == '\\') {
                        escaped = true;
                    } else if (cur == '"' && escaped == false) {
                        valid = true;
                        break;
                    } else {
                        escaped = false;
                    }
                    sb.append(cur);
                } // WHILE
                if (valid == false) {
                    throw new Exception("Invalid parameter string '" + sb + "'");
                }
                params.add(sb.toString());

            // Otherwise just grab the substring to the next space
            } else {
                int next = paramStr.indexOf(" ", pos);
                if (next == -1) {
                    params.add(paramStr.substring(pos));
                    pos = len;
                } else {
                    params.add(paramStr.substring(pos, next));
                    pos = next;
                }
            }
        }
        return (params);
    }
   
    private String formatResult(ClientResponse cr) {
        final VoltTable results[] = cr.getResults();
        final int num_results = results.length;
        StringBuilder sb = new StringBuilder();
       
        if (this.enable_debug) {
            sb.append(cr.toString());
        }
        else {
            // MAIN BODY
            if (this.enable_csv) {
                StringWriter out = new StringWriter();
                for (int i = 0; i < num_results; i++) {
                    if (i > 0) out.write("\n\n");
                    VoltTableUtil.csv(out, results[i], true);
                } // FOR
                sb.append(out.toString());
            } else {
                sb.append(VoltTableUtil.format(results));
            }
           
            // FOOTER
            String footer = "";
            if (this.enable_csv == false) {
                if (num_results == 1) {
                    int row_count = results[0].getRowCount();
                    footer = String.format("%d row%s in set", row_count, (row_count > 1 ? "s" : ""));
                }
                else if (num_results == 0) {
                    footer = "No results returned";
                }
                else {
                    footer = num_results + " tables returned";
                }
                sb.append(String.format("\n%s (%.2f sec)\n", footer, (cr.getClientRoundtrip() / 1000d)));
            }
        }
        return (sb.toString());
    }

    public static void main(String vargs[]) throws Exception {
        ArgumentsParser args = ArgumentsParser.load(vargs,
                ArgumentsParser.PARAM_CATALOG
        );
        HStoreTerminal term = new HStoreTerminal(args.catalog);
       
        // CSV OUTPUT
        if (args.hasBooleanParam(ArgumentsParser.PARAM_TERMINAL_CSV)) {
            term.enable_csv = args.getBooleanParam(ArgumentsParser.PARAM_TERMINAL_CSV);
        }
        // HOSTNAME
        if (args.hasParam(ArgumentsParser.PARAM_TERMINAL_HOST)) {
            term.hostname = args.getParam(ArgumentsParser.PARAM_TERMINAL_HOST);
        }
        // PORT
        if (args.hasParam(ArgumentsParser.PARAM_TERMINAL_PORT)) {
            term.port = args.getIntParam(ArgumentsParser.PARAM_TERMINAL_PORT);
        }
       
        term.run();
    }

}
TOP

Related Classes of edu.brown.terminal.HStoreTerminal$TerminalConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.