org.apache.zeppelin.postgresql.PostgreSqlInterpreter.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.zeppelin.postgresql.PostgreSqlInterpreter.java

Source

/**
 * 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.
 */
package org.apache.zeppelin.postgresql;

import static org.apache.commons.lang.StringUtils.containsIgnoreCase;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterPropertyBuilder;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.scheduler.Scheduler;
import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;

/**
 * PostgreSQL interpreter for Zeppelin. This interpreter can also be used for accessing HAWQ and
 * GreenplumDB.
 * 
 * <ul>
 * <li>{@code postgresql.url} - JDBC URL to connect to.</li>
 * <li>{@code postgresql.user} - JDBC user name..</li>
 * <li>{@code postgresql.password} - JDBC password..</li>
 * <li>{@code postgresql.driver.name} - JDBC driver name.</li>
 * <li>{@code postgresql.max.result} - Max number of SQL result to display.</li>
 * </ul>
 * 
 * <p>
 * How to use: <br/>
 * {@code %psql.sql} <br/>
 * {@code 
 *  SELECT store_id, count(*) 
 *  FROM retail_demo.order_lineitems_pxf 
 *  GROUP BY store_id;
 * }
 * </p>
 * 
 * For SQL auto-completion use the (Ctrl+.) shortcut.
 */
public class PostgreSqlInterpreter extends Interpreter {

    private Logger logger = LoggerFactory.getLogger(PostgreSqlInterpreter.class);

    private static final char WhITESPACE = ' ';
    private static final char NEWLINE = '\n';
    private static final char TAB = '\t';
    private static final String TABLE_MAGIC_TAG = "%table ";
    private static final String EXPLAIN_PREDICATE = "EXPLAIN ";
    private static final String UPDATE_COUNT_HEADER = "Update Count";

    static final String DEFAULT_JDBC_URL = "jdbc:postgresql://localhost:5432/";
    static final String DEFAULT_JDBC_USER_PASSWORD = "";
    static final String DEFAULT_JDBC_USER_NAME = "gpadmin";
    static final String DEFAULT_JDBC_DRIVER_NAME = "org.postgresql.Driver";
    static final String DEFAULT_MAX_RESULT = "1000";

    static final String POSTGRESQL_SERVER_URL = "postgresql.url";
    static final String POSTGRESQL_SERVER_USER = "postgresql.user";
    static final String POSTGRESQL_SERVER_PASSWORD = "postgresql.password";
    static final String POSTGRESQL_SERVER_DRIVER_NAME = "postgresql.driver.name";
    static final String POSTGRESQL_SERVER_MAX_RESULT = "postgresql.max.result";
    static final String EMPTY_COLUMN_VALUE = "";

    static {
        Interpreter.register("sql", "psql", PostgreSqlInterpreter.class.getName(), new InterpreterPropertyBuilder()
                .add(POSTGRESQL_SERVER_URL, DEFAULT_JDBC_URL, "The URL for PostgreSQL.")
                .add(POSTGRESQL_SERVER_USER, DEFAULT_JDBC_USER_NAME, "The PostgreSQL user name")
                .add(POSTGRESQL_SERVER_PASSWORD, DEFAULT_JDBC_USER_PASSWORD, "The PostgreSQL user password")
                .add(POSTGRESQL_SERVER_DRIVER_NAME, DEFAULT_JDBC_DRIVER_NAME, "JDBC Driver Name")
                .add(POSTGRESQL_SERVER_MAX_RESULT, DEFAULT_MAX_RESULT, "Max number of SQL result to display.")
                .build());
    }

    private Connection jdbcConnection;
    private Statement currentStatement;
    private Exception exceptionOnConnect;
    private int maxResult;

    private SqlCompleter sqlCompleter;

    private static final Function<CharSequence, String> sequenceToStringTransformer = new Function<CharSequence, String>() {
        public String apply(CharSequence seq) {
            return seq.toString();
        }
    };

    private static final List<String> NO_COMPLETION = new ArrayList<String>();

    public PostgreSqlInterpreter(Properties property) {
        super(property);
    }

    @Override
    public void open() {

        logger.info("Open psql connection!");

        // Ensure that no previous connections are left open.
        close();

        try {

            String driverName = getProperty(POSTGRESQL_SERVER_DRIVER_NAME);
            String url = getProperty(POSTGRESQL_SERVER_URL);
            String user = getProperty(POSTGRESQL_SERVER_USER);
            String password = getProperty(POSTGRESQL_SERVER_PASSWORD);
            maxResult = Integer.valueOf(getProperty(POSTGRESQL_SERVER_MAX_RESULT));

            Class.forName(driverName);

            jdbcConnection = DriverManager.getConnection(url, user, password);

            sqlCompleter = createSqlCompleter(jdbcConnection);

            exceptionOnConnect = null;
            logger.info("Successfully created psql connection");

        } catch (ClassNotFoundException | SQLException e) {
            logger.error("Cannot open connection", e);
            exceptionOnConnect = e;
            close();
        }
    }

    private SqlCompleter createSqlCompleter(Connection jdbcConnection) {

        SqlCompleter completer = null;
        try {
            Set<String> keywordsCompletions = SqlCompleter.getSqlKeywordsCompletions(jdbcConnection);
            Set<String> dataModelCompletions = SqlCompleter.getDataModelMetadataCompletions(jdbcConnection);
            SetView<String> allCompletions = Sets.union(keywordsCompletions, dataModelCompletions);
            completer = new SqlCompleter(allCompletions, dataModelCompletions);

        } catch (IOException | SQLException e) {
            logger.error("Cannot create SQL completer", e);
        }

        return completer;
    }

    @Override
    public void close() {

        logger.info("Close psql connection!");

        try {
            if (getJdbcConnection() != null) {
                getJdbcConnection().close();
            }
        } catch (SQLException e) {
            logger.error("Cannot close connection", e);
        } finally {
            exceptionOnConnect = null;
        }
    }

    private InterpreterResult executeSql(String sql) {
        try {

            if (exceptionOnConnect != null) {
                return new InterpreterResult(Code.ERROR, exceptionOnConnect.getMessage());
            }

            currentStatement = getJdbcConnection().createStatement();

            currentStatement.setMaxRows(maxResult);

            StringBuilder msg = null;
            boolean isTableType = false;

            if (containsIgnoreCase(sql, EXPLAIN_PREDICATE)) {
                msg = new StringBuilder();
            } else {
                msg = new StringBuilder(TABLE_MAGIC_TAG);
                isTableType = true;
            }

            ResultSet resultSet = null;
            try {

                boolean isResultSetAvailable = currentStatement.execute(sql);

                if (isResultSetAvailable) {
                    resultSet = currentStatement.getResultSet();

                    ResultSetMetaData md = resultSet.getMetaData();

                    for (int i = 1; i < md.getColumnCount() + 1; i++) {
                        if (i > 1) {
                            msg.append(TAB);
                        }
                        msg.append(replaceReservedChars(isTableType, md.getColumnName(i)));
                    }
                    msg.append(NEWLINE);

                    int displayRowCount = 0;
                    while (resultSet.next() && displayRowCount < getMaxResult()) {
                        for (int i = 1; i < md.getColumnCount() + 1; i++) {
                            msg.append(replaceReservedChars(isTableType, resultSet.getString(i)));
                            if (i != md.getColumnCount()) {
                                msg.append(TAB);
                            }
                        }
                        msg.append(NEWLINE);
                        displayRowCount++;
                    }
                } else {
                    // Response contains either an update count or there are no results.
                    int updateCount = currentStatement.getUpdateCount();
                    msg.append(UPDATE_COUNT_HEADER).append(NEWLINE);
                    msg.append(updateCount).append(NEWLINE);

                    // In case of update event (e.g. isResultSetAvailable = false) update the completion
                    // meta-data.
                    if (sqlCompleter != null) {
                        sqlCompleter.updateDataModelMetaData(getJdbcConnection());
                    }
                }
            } finally {
                try {
                    if (resultSet != null) {
                        resultSet.close();
                    }
                    currentStatement.close();
                } finally {
                    currentStatement = null;
                }
            }

            return new InterpreterResult(Code.SUCCESS, msg.toString());

        } catch (SQLException ex) {
            logger.error("Cannot run " + sql, ex);
            return new InterpreterResult(Code.ERROR, ex.getMessage());
        }
    }

    /**
     * For %table response replace Tab and Newline characters from the content.
     */
    private String replaceReservedChars(boolean isTableResponseType, String str) {
        if (str == null) {
            return EMPTY_COLUMN_VALUE;
        }
        return (!isTableResponseType) ? str : str.replace(TAB, WhITESPACE).replace(NEWLINE, WhITESPACE);
    }

    @Override
    public InterpreterResult interpret(String cmd, InterpreterContext contextInterpreter) {
        logger.info("Run SQL command '{}'", cmd);
        return executeSql(cmd);
    }

    @Override
    public void cancel(InterpreterContext context) {

        logger.info("Cancel current query statement.");

        if (currentStatement != null) {
            try {
                currentStatement.cancel();
            } catch (SQLException ex) {
            } finally {
                currentStatement = null;
            }
        }
    }

    @Override
    public FormType getFormType() {
        return FormType.SIMPLE;
    }

    @Override
    public int getProgress(InterpreterContext context) {
        return 0;
    }

    @Override
    public Scheduler getScheduler() {
        return SchedulerFactory.singleton()
                .createOrGetFIFOScheduler(PostgreSqlInterpreter.class.getName() + this.hashCode());
    }

    @Override
    public List<String> completion(String buf, int cursor) {

        List<CharSequence> candidates = new ArrayList<CharSequence>();
        if (sqlCompleter != null && sqlCompleter.complete(buf, cursor, candidates) >= 0) {
            return Lists.transform(candidates, sequenceToStringTransformer);
        } else {
            return NO_COMPLETION;
        }
    }

    public int getMaxResult() {
        return maxResult;
    }

    // Test only method
    protected Connection getJdbcConnection() {
        return jdbcConnection;
    }
}