zeppelin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From m...@apache.org
Subject incubator-zeppelin git commit: ZEPPELIN-70: Add PostgreSQL Interpreter
Date Sun, 09 Aug 2015 19:51:32 GMT
Repository: incubator-zeppelin
Updated Branches:
  refs/heads/master dcfa3b5df -> cc5b4bcfa


ZEPPELIN-70: Add PostgreSQL Interpreter

	modified:   conf/zeppelin-site.xml.template
	modified:   pom.xml
	new file:   postgresql/pom.xml
	new file:   postgresql/src/main/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreter.java
	new file:   postgresql/src/test/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreterTest.java

Author: tzolov <christian.tzolov@gmail.com>

Closes #172 from tzolov/ZEPPELIN-70 and squashes the following commits:

5de507d [tzolov] ZEPPELIN-70: Remove the obsolete dependencies from the POM
a3db5f7 [tzolov] ZEPPELING-70: Rebase to upstream master
745ad3d [tzolov] ZEPPELIN-70: Add PostgreSQL JDBC license statement
a7d26dd [tzolov] ZEPPELIN-70: Closed previous connection before open new one. Register psql
in ZeppelinConfiguration
886eab5 [tzolov] ZEPPELIN-70: Add property to control the number of SQL results shown in the
display.
880bde0 [tzolov] ZEPPELIN-70: Improve javadoc. Clean pom formating
ba1b5b8 [tzolov] ZEPPELIN-70: Fix spelling
60544a1 [tzolov] ZEPPELIN-70: Add jdbc driver name as configuration property
841d67c [tzolov] ZEPPELIN-70: Add PostgreSQL Interpreter


Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/cc5b4bcf
Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/cc5b4bcf
Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/cc5b4bcf

Branch: refs/heads/master
Commit: cc5b4bcfa734221eb13915b045339fbefbb96f42
Parents: dcfa3b5
Author: tzolov <christian.tzolov@gmail.com>
Authored: Sun Aug 9 07:08:07 2015 +0200
Committer: Lee moon soo <moon@apache.org>
Committed: Sun Aug 9 12:51:27 2015 -0700

----------------------------------------------------------------------
 LICENSE                                         |  37 +++
 conf/zeppelin-site.xml.template                 |   2 +-
 pom.xml                                         |   1 +
 postgresql/pom.xml                              | 151 ++++++++++
 .../postgresql/PostgreSqlInterpreter.java       | 278 +++++++++++++++++++
 .../postgresql/PostgreSqlInterpreterTest.java   | 224 +++++++++++++++
 .../zeppelin/conf/ZeppelinConfiguration.java    |   3 +-
 7 files changed, 694 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/cc5b4bcf/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index aec76a6..0fb8237 100644
--- a/LICENSE
+++ b/LICENSE
@@ -200,3 +200,40 @@
    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.
+
+
+   APACHE ZEPPELIN SUBCOMPONENTS:
+
+   The Apache Zeppelin project contains subcomponents with separate copyright
+   notices and license terms. Your use of the source code for the these
+   subcomponents is subject to the terms and conditions of the following
+   licenses.
+
+   For the PostgreSQL JDBC driver jar file:
+
+   Copyright (c) 1997-2011, PostgreSQL Global Development Group
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+   3. Neither the name of the PostgreSQL Global Development Group nor the names
+      of its contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+   POSSIBILITY OF SUCH DAMAGE.

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/cc5b4bcf/conf/zeppelin-site.xml.template
----------------------------------------------------------------------
diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index f48a960..6d03f6e 100644
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -72,7 +72,7 @@
 
 <property>
   <name>zeppelin.interpreters</name>
-  <value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter</value>
+  <value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter</value>
   <description>Comma separated interpreter configurations. First interpreter become
a default</description>
 </property>
 

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/cc5b4bcf/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index ecdebdd..791681b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -92,6 +92,7 @@
     <module>shell</module>
     <module>hive</module>
     <module>geode</module>
+    <module>postgresql</module>
     <module>tajo</module>
     <module>flink</module>
     <module>ignite</module>

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/cc5b4bcf/postgresql/pom.xml
----------------------------------------------------------------------
diff --git a/postgresql/pom.xml b/postgresql/pom.xml
new file mode 100644
index 0000000..1a64c91
--- /dev/null
+++ b/postgresql/pom.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>zeppelin</artifactId>
+    <groupId>org.apache.zeppelin</groupId>
+    <version>0.6.0-incubating-SNAPSHOT</version>
+  </parent>
+
+  <groupId>org.apache.zeppelin</groupId>
+  <artifactId>zeppelin-postgresql</artifactId>
+  <packaging>jar</packaging>
+  <version>0.6.0-incubating-SNAPSHOT</version>
+  <name>Zeppelin: PostgreSQL interpreter</name>
+  <url>http://www.apache.org</url>
+
+  <properties>
+    <postgresql.version>9.4-1201-jdbc41</postgresql.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.zeppelin</groupId>
+      <artifactId>zeppelin-interpreter</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.postgresql</groupId>
+      <artifactId>postgresql</artifactId>
+      <version>${postgresql.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <version>1.9.5</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>com.mockrunner</groupId>
+      <artifactId>mockrunner-jdbc</artifactId>
+      <version>1.0.8</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <version>2.7</version>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <version>1.3.1</version>
+        <executions>
+          <execution>
+            <id>enforce</id>
+            <phase>none</phase>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <version>2.8</version>
+        <executions>
+          <execution>
+            <id>copy-dependencies</id>
+            <phase>package</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${project.build.directory}/../../interpreter/psql</outputDirectory>
+              <overWriteReleases>false</overWriteReleases>
+              <overWriteSnapshots>false</overWriteSnapshots>
+              <overWriteIfNewer>true</overWriteIfNewer>
+              <includeScope>runtime</includeScope>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-artifact</id>
+            <phase>package</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${project.build.directory}/../../interpreter/psql</outputDirectory>
+              <overWriteReleases>false</overWriteReleases>
+              <overWriteSnapshots>false</overWriteSnapshots>
+              <overWriteIfNewer>true</overWriteIfNewer>
+              <includeScope>runtime</includeScope>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>${project.groupId}</groupId>
+                  <artifactId>${project.artifactId}</artifactId>
+                  <version>${project.version}</version>
+                  <type>${project.packaging}</type>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/cc5b4bcf/postgresql/src/main/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreter.java
----------------------------------------------------------------------
diff --git a/postgresql/src/main/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreter.java
b/postgresql/src/main/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreter.java
new file mode 100644
index 0000000..7ac18d1
--- /dev/null
+++ b/postgresql/src/main/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreter.java
@@ -0,0 +1,278 @@
+/**
+ * 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 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.List;
+import java.util.Properties;
+
+import org.apache.commons.lang.StringUtils;
+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;
+
+/**
+ * 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>
+ */
+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 {
+    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;
+
+  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);
+
+      exceptionOnConnect = null;
+      logger.info("Successfully created psql connection");
+
+    } catch (ClassNotFoundException | SQLException e) {
+      logger.error("Cannot open connection", e);
+      exceptionOnConnect = e;
+    }
+  }
+
+  @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();
+
+      StringBuilder msg = null;
+      boolean isTableType = false;
+
+      if (StringUtils.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);
+        }
+      } 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) {
+    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) {
+    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) {
+    return null;
+  }
+
+  public int getMaxResult() {
+    return maxResult;
+  }
+
+  // Test only method
+  protected Connection getJdbcConnection() {
+    return jdbcConnection;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/cc5b4bcf/postgresql/src/test/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreterTest.java
----------------------------------------------------------------------
diff --git a/postgresql/src/test/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreterTest.java
b/postgresql/src/test/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreterTest.java
new file mode 100644
index 0000000..df793a6
--- /dev/null
+++ b/postgresql/src/test/java/org/apache/zeppelin/postgresql/PostgreSqlInterpreterTest.java
@@ -0,0 +1,224 @@
+/**
+ * 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.zeppelin.postgresql.PostgreSqlInterpreter.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+
+import java.sql.SQLException;
+import java.util.Properties;
+
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+
+import com.mockrunner.jdbc.BasicJDBCTestCaseAdapter;
+import com.mockrunner.jdbc.StatementResultSetHandler;
+import com.mockrunner.mock.jdbc.MockConnection;
+import com.mockrunner.mock.jdbc.MockResultSet;
+
+/**
+ * PostgreSQL interpreter unit tests
+ */
+public class PostgreSqlInterpreterTest extends BasicJDBCTestCaseAdapter {
+
+  private PostgreSqlInterpreter psqlInterpreter = null;
+  private MockResultSet result = null;
+
+  @Before
+  public void beforeTest() {
+    MockConnection connection = getJDBCMockObjectFactory().getMockConnection();
+
+    StatementResultSetHandler statementHandler = connection.getStatementResultSetHandler();
+    result = statementHandler.createResultSet();
+    statementHandler.prepareGlobalResultSet(result);
+
+    Properties properties = new Properties();
+    properties.put(POSTGRESQL_SERVER_DRIVER_NAME, DEFAULT_JDBC_DRIVER_NAME);
+    properties.put(POSTGRESQL_SERVER_URL, DEFAULT_JDBC_URL);
+    properties.put(POSTGRESQL_SERVER_USER, DEFAULT_JDBC_USER_NAME);
+    properties.put(POSTGRESQL_SERVER_PASSWORD, DEFAULT_JDBC_USER_PASSWORD);
+    properties.put(POSTGRESQL_SERVER_MAX_RESULT, DEFAULT_MAX_RESULT);
+
+    psqlInterpreter = spy(new PostgreSqlInterpreter(properties));
+    when(psqlInterpreter.getJdbcConnection()).thenReturn(connection);
+  }
+
+  @Test
+  public void testOpenCommandIndempotency() throws SQLException {
+    // Ensure that an attempt to open new connection will clean any remaining connections
+    psqlInterpreter.open();
+    psqlInterpreter.open();
+    psqlInterpreter.open();
+
+    verify(psqlInterpreter, times(3)).open();
+    verify(psqlInterpreter, times(3)).close();
+  }
+
+  @Test
+  public void testDefaultProperties() throws SQLException {
+
+    PostgreSqlInterpreter psqlInterpreter = new PostgreSqlInterpreter(new Properties());
+
+    assertEquals(DEFAULT_JDBC_DRIVER_NAME,
+        psqlInterpreter.getProperty(POSTGRESQL_SERVER_DRIVER_NAME));
+    assertEquals(DEFAULT_JDBC_URL, psqlInterpreter.getProperty(POSTGRESQL_SERVER_URL));
+    assertEquals(DEFAULT_JDBC_USER_NAME, psqlInterpreter.getProperty(POSTGRESQL_SERVER_USER));
+    assertEquals(DEFAULT_JDBC_USER_PASSWORD,
+        psqlInterpreter.getProperty(POSTGRESQL_SERVER_PASSWORD));
+    assertEquals(DEFAULT_MAX_RESULT, psqlInterpreter.getProperty(POSTGRESQL_SERVER_MAX_RESULT));
+  }
+
+  @Test
+  public void testConnectionClose() throws SQLException {
+
+    PostgreSqlInterpreter psqlInterpreter = spy(new PostgreSqlInterpreter(new Properties()));
+
+    when(psqlInterpreter.getJdbcConnection()).thenReturn(
+        getJDBCMockObjectFactory().getMockConnection());
+
+    psqlInterpreter.close();
+
+    verifyAllResultSetsClosed();
+    verifyAllStatementsClosed();
+    verifyConnectionClosed();
+  }
+
+  @Test
+  public void testStatementCancel() throws SQLException {
+
+    PostgreSqlInterpreter psqlInterpreter = spy(new PostgreSqlInterpreter(new Properties()));
+
+    when(psqlInterpreter.getJdbcConnection()).thenReturn(
+        getJDBCMockObjectFactory().getMockConnection());
+
+    psqlInterpreter.cancel(null);
+
+    verifyAllResultSetsClosed();
+    verifyAllStatementsClosed();
+    assertFalse("Cancel operation should not close the connection", psqlInterpreter
+        .getJdbcConnection().isClosed());
+  }
+
+  @Test
+  public void testSelectQuery() throws SQLException {
+
+    when(psqlInterpreter.getMaxResult()).thenReturn(1000);
+
+    String sqlQuery = "select * from t";
+
+    result.addColumn("col1", new String[] {"val11", "val12"});
+    result.addColumn("col2", new String[] {"val21", "val22"});
+
+    InterpreterResult interpreterResult = psqlInterpreter.interpret(sqlQuery, null);
+
+    assertEquals(InterpreterResult.Code.SUCCESS, interpreterResult.code());
+    assertEquals(InterpreterResult.Type.TABLE, interpreterResult.type());
+    assertEquals("col1\tcol2\nval11\tval21\nval12\tval22\n", interpreterResult.message());
+
+    verifySQLStatementExecuted(sqlQuery);
+    verifyAllResultSetsClosed();
+    verifyAllStatementsClosed();
+  }
+
+  @Test
+  public void testSelectQueryMaxResult() throws SQLException {
+
+    when(psqlInterpreter.getMaxResult()).thenReturn(1);
+
+    String sqlQuery = "select * from t";
+
+    result.addColumn("col1", new String[] {"val11", "val12"});
+    result.addColumn("col2", new String[] {"val21", "val22"});
+
+    InterpreterResult interpreterResult = psqlInterpreter.interpret(sqlQuery, null);
+
+    assertEquals(InterpreterResult.Code.SUCCESS, interpreterResult.code());
+    assertEquals(InterpreterResult.Type.TABLE, interpreterResult.type());
+    assertEquals("col1\tcol2\nval11\tval21\n", interpreterResult.message());
+
+    verifySQLStatementExecuted(sqlQuery);
+    verifyAllResultSetsClosed();
+    verifyAllStatementsClosed();
+  }
+
+  @Test
+  public void testSelectQueryWithSpecialCharacters() throws SQLException {
+
+    when(psqlInterpreter.getMaxResult()).thenReturn(1000);
+
+    String sqlQuery = "select * from t";
+
+    result.addColumn("co\tl1", new String[] {"val11", "va\tl1\n2"});
+    result.addColumn("co\nl2", new String[] {"v\nal21", "val\t22"});
+
+    InterpreterResult interpreterResult = psqlInterpreter.interpret(sqlQuery, null);
+
+    assertEquals(InterpreterResult.Code.SUCCESS, interpreterResult.code());
+    assertEquals(InterpreterResult.Type.TABLE, interpreterResult.type());
+    assertEquals("co l1\tco l2\nval11\tv al21\nva l1 2\tval 22\n", interpreterResult.message());
+
+    verifySQLStatementExecuted(sqlQuery);
+    verifyAllResultSetsClosed();
+    verifyAllStatementsClosed();
+  }
+
+  @Test
+  public void testExplainQuery() throws SQLException {
+
+    when(psqlInterpreter.getMaxResult()).thenReturn(1000);
+
+    String sqlQuery = "explain select * from t";
+
+    result.addColumn("col1", new String[] {"val11", "val12"});
+
+    InterpreterResult interpreterResult = psqlInterpreter.interpret(sqlQuery, null);
+
+    assertEquals(InterpreterResult.Code.SUCCESS, interpreterResult.code());
+    assertEquals(InterpreterResult.Type.TEXT, interpreterResult.type());
+    assertEquals("col1\nval11\nval12\n", interpreterResult.message());
+
+    verifySQLStatementExecuted(sqlQuery);
+    verifyAllResultSetsClosed();
+    verifyAllStatementsClosed();
+  }
+
+  @Test
+  public void testExplainQueryWithSpecialCharachters() throws SQLException {
+
+    when(psqlInterpreter.getMaxResult()).thenReturn(1000);
+
+    String sqlQuery = "explain select * from t";
+
+    result.addColumn("co\tl\n1", new String[] {"va\nl11", "va\tl\n12"});
+
+    InterpreterResult interpreterResult = psqlInterpreter.interpret(sqlQuery, null);
+
+    assertEquals(InterpreterResult.Code.SUCCESS, interpreterResult.code());
+    assertEquals(InterpreterResult.Type.TEXT, interpreterResult.type());
+    assertEquals("co\tl\n1\nva\nl11\nva\tl\n12\n", interpreterResult.message());
+
+    verifySQLStatementExecuted(sqlQuery);
+    verifyAllResultSetsClosed();
+    verifyAllStatementsClosed();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/cc5b4bcf/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index e25d3c0..0d2eb58 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -395,7 +395,8 @@ public class ZeppelinConfiguration extends XMLConfiguration {
         + "org.apache.zeppelin.ignite.IgniteSqlInterpreter,"
         + "org.apache.zeppelin.lens.LensInterpreter,"
         + "org.apache.zeppelin.cassandra.CassandraInterpreter,"
-        + "org.apache.zeppelin.geode.GeodeOqlInterpreter"),
+        + "org.apache.zeppelin.geode.GeodeOqlInterpreter,"
+        + "org.apache.zeppelin.postgresql.PostgreSqlInterpreter"),
     ZEPPELIN_INTERPRETER_DIR("zeppelin.interpreter.dir", "interpreter"),
     ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT("zeppelin.interpreter.connect.timeout", 30000),
     ZEPPELIN_ENCODING("zeppelin.encoding", "UTF-8"),


Mime
View raw message