Repository: incubator-zeppelin
Updated Branches:
  refs/heads/master cbef1be48 -> 7ba743eec


ZEPPELIN-186: Apache Phoenix interpreter

Include an interpreter for Apache Phoenix

Almost identical to Hive interpreter. Could/should generic JDBC interpreters be 
combined in future work?

Author: Randy Gelhausen <[email protected]>
Author: = <[email protected]>

Closes #167 from randerzander/phoenix-interpreter and squashes the following 
commits:

72b5c71 [=] bumped HBase version down to 1.0
bbaadc3 [=] Switched to Apache artifacts
0a3f8cf [=] Merge branch 'master' of github.com:apache/incubator-zeppelin into 
phoenix-interpreter
060fa44 [Randy Gelhausen] Moved currentStatement and resultSet to finally, 
added commit call to finally
531b7b4 [Randy Gelhausen] Moved result set close to finally block
df407ec [Randy Gelhausen] Made Update output more clear
8282cc2 [Randy Gelhausen] fixed handling of null values in PhoenixInterpreter
b69e7aa [Randy Gelhausen] cleaned up PhoenixInterpreter and tests, switched to 
execute from executeQuery
b7ad803 [Randy Gelhausen] Merge branch 'master' of 
github.com:apache/incubator-zeppelin into phoenix-interpreter
1b24d47 [Randy Gelhausen] Merge branch 'master' of 
github.com:apache/incubator-zeppelin into phoenix-interpreter
c99d66d [Randy Gelhausen] added safety call to close() before open, added 
maxResult check
8c405a8 [Randy Gelhausen] Fixed pom for phoenix jdbc driver dependency 
inclusion, updated interpreter properties
0f8e18b [Randy Gelhausen] fixed old reference to ZK_URL
6e816bc [Randy Gelhausen] Updated zeppelin-site.xml.template to include 
PhoenixInterpreter, changed Phoenix properties from zk.url to phoenix.url
d7716cc [Randy Gelhausen] fixed hive.PhoenixInterpreter to 
phoenix.PhoenixInterpreter, set phoenix magic to pql
1d568ed [Randy Gelhausen] Added Phoenix to ZeppelinConfiguration.java
96db8b5 [Randy Gelhausen] fixing default phoenix.url
2f3fb54 [Randy Gelhausen] initial commit for phoenix 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/7ba743ee
Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/7ba743ee
Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/7ba743ee

Branch: refs/heads/master
Commit: 7ba743eec77819397460c91b6fa49dfa4604c5de
Parents: cbef1be
Author: Randy Gelhausen <[email protected]>
Authored: Wed Aug 19 18:49:44 2015 -0400
Committer: Lee moon soo <[email protected]>
Committed: Sun Aug 23 19:43:16 2015 -0700

----------------------------------------------------------------------
 conf/zeppelin-site.xml.template                 |   2 +-
 phoenix/pom.xml                                 | 143 +++++++++++
 .../zeppelin/phoenix/PhoenixInterpreter.java    | 239 +++++++++++++++++++
 .../phoenix/PhoenixInterpreterTest.java         | 226 ++++++++++++++++++
 pom.xml                                         |   1 +
 .../zeppelin/conf/ZeppelinConfiguration.java    |   1 +
 6 files changed, 611 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7ba743ee/conf/zeppelin-site.xml.template
----------------------------------------------------------------------
diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index 5d3b97d..ad90f88 100644
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -85,7 +85,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,org.apache.zeppelin.postgresql.PostgreSqlInterpreter</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,org.apache.zeppelin.phoenix.PhoenixInterpreter</value>
   <description>Comma separated interpreter configurations. First interpreter 
become a default</description>
 </property>
 

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7ba743ee/phoenix/pom.xml
----------------------------------------------------------------------
diff --git a/phoenix/pom.xml b/phoenix/pom.xml
new file mode 100644
index 0000000..3cca32d
--- /dev/null
+++ b/phoenix/pom.xml
@@ -0,0 +1,143 @@
+<?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/xsd/maven-4.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-phoenix</artifactId>
+  <packaging>jar</packaging>
+  <version>0.6.0-incubating-SNAPSHOT</version>
+  <name>Zeppelin: Apache Phoenix Interpreter</name>
+  <description>Zeppelin interprter for Apache Phoenix</description>
+  <url>http://zeppelin.incubator.apache.org</url>
+  
+  <properties>
+    <phoenix.version>4.4.0-HBase-1.0</phoenix.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.zeppelin</groupId>
+      <artifactId>zeppelin-interpreter</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.phoenix</groupId>
+      <artifactId>phoenix-core</artifactId>
+      <version>${phoenix.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/phoenix</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/phoenix</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/7ba743ee/phoenix/src/main/java/org/apache/zeppelin/phoenix/PhoenixInterpreter.java
----------------------------------------------------------------------
diff --git 
a/phoenix/src/main/java/org/apache/zeppelin/phoenix/PhoenixInterpreter.java 
b/phoenix/src/main/java/org/apache/zeppelin/phoenix/PhoenixInterpreter.java
new file mode 100644
index 0000000..c90aece
--- /dev/null
+++ b/phoenix/src/main/java/org/apache/zeppelin/phoenix/PhoenixInterpreter.java
@@ -0,0 +1,239 @@
+/**
+ * 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.phoenix;
+
+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;
+
+/**
+ * Phoenix interpreter for Zeppelin.
+ */
+public class PhoenixInterpreter extends Interpreter {
+  Logger logger = LoggerFactory.getLogger(PhoenixInterpreter.class);
+
+  private static final String EXPLAIN_PREDICATE = "EXPLAIN ";
+  private static final String UPDATE_HEADER = "UPDATES ";
+
+  private static final String WS = " ";
+  private static final String NEWLINE = "\n";
+  private static final String TAB = "\t";
+  private static final String TABLE_MAGIC_TAG = "%table ";
+
+  static final String PHOENIX_JDBC_URL = "phoenix.jdbc.url";
+  static final String PHOENIX_JDBC_USER = "phoenix.user";
+  static final String PHOENIX_JDBC_PASSWORD = "phoenix.password";
+  static final String PHOENIX_MAX_RESULT = "phoenix.max.result";
+  static final String PHOENIX_JDBC_DRIVER_NAME = "phoenix.driver.name";
+
+  static final String DEFAULT_JDBC_URL = 
"jdbc:phoenix:localhost:2181:/hbase-unsecure";
+  static final String DEFAULT_JDBC_USER = "";
+  static final String DEFAULT_JDBC_PASSWORD = "";
+  static final String DEFAULT_MAX_RESULT = "1000";
+  static final String DEFAULT_JDBC_DRIVER_NAME = 
"org.apache.phoenix.jdbc.PhoenixDriver";
+
+  private Connection jdbcConnection;
+  private Statement currentStatement;
+  private Exception exceptionOnConnect;
+  private int maxResult;
+
+  static {
+    Interpreter.register(
+      "sql",
+      "phoenix",
+      PhoenixInterpreter.class.getName(),
+      new InterpreterPropertyBuilder()
+        .add(PHOENIX_JDBC_URL, DEFAULT_JDBC_URL, "Phoenix JDBC connection 
string")
+        .add(PHOENIX_JDBC_USER, DEFAULT_JDBC_USER, "The Phoenix user")
+        .add(PHOENIX_JDBC_PASSWORD, DEFAULT_JDBC_PASSWORD, "The password for 
the Phoenix user")
+        .add(PHOENIX_MAX_RESULT, DEFAULT_MAX_RESULT, "Max number of SQL 
results to display.")
+        .add(PHOENIX_JDBC_DRIVER_NAME, DEFAULT_JDBC_DRIVER_NAME, "Phoenix 
Driver classname.")
+        .build()
+    );
+  }
+
+  public PhoenixInterpreter(Properties property) {
+    super(property);
+  }
+
+  @Override
+  public void open() {
+    logger.info("Jdbc open connection called!");
+    close();
+
+    try {
+      Class.forName(getProperty(PHOENIX_JDBC_DRIVER_NAME));
+
+      maxResult = Integer.valueOf(getProperty(PHOENIX_MAX_RESULT));
+      jdbcConnection = DriverManager.getConnection(
+        getProperty(PHOENIX_JDBC_URL),
+        getProperty(PHOENIX_JDBC_USER),
+        getProperty(PHOENIX_JDBC_PASSWORD)
+      );
+      exceptionOnConnect = null;
+      logger.info("Successfully created Jdbc connection");
+    }
+    catch (ClassNotFoundException | SQLException e) {
+      logger.error("Cannot open connection", e);
+      exceptionOnConnect = e;
+    }
+  }
+
+  @Override
+  public void close() {
+    logger.info("Jdbc close connection called!");
+
+    try {
+      if (getJdbcConnection() != null) {
+        getJdbcConnection().close();
+      }
+    } catch (SQLException e) {
+      logger.error("Cannot close connection", e);
+    }
+    finally {
+      exceptionOnConnect = null;
+    }
+  }
+
+  private String clean(boolean isExplain, String str){
+    return (isExplain || str == null) ? str : str.replace(TAB, 
WS).replace(NEWLINE, WS);  
+  }
+
+  private InterpreterResult executeSql(String sql) {
+    try {
+      if (exceptionOnConnect != null) {
+        return new InterpreterResult(Code.ERROR, 
exceptionOnConnect.getMessage());
+      }
+
+      currentStatement = getJdbcConnection().createStatement();
+
+      boolean isExplain = StringUtils.containsIgnoreCase(sql, 
EXPLAIN_PREDICATE);
+      StringBuilder msg = (isExplain) ? new StringBuilder() : new 
StringBuilder(TABLE_MAGIC_TAG);
+
+      ResultSet res = null;
+      try {
+        boolean hasResult = currentStatement.execute(sql);
+        if (hasResult){ //If query had results
+          res = currentStatement.getResultSet();
+          //Append column names
+          ResultSetMetaData md = res.getMetaData();
+          String row = clean(isExplain, md.getColumnName(1));
+          for (int i = 2; i < md.getColumnCount() + 1; i++)
+            row += TAB + clean(isExplain, md.getColumnName(i));
+          msg.append(row + NEWLINE);
+
+          //Append rows
+          int rowCount = 0;
+          while (res.next() && rowCount < getMaxResult()) {
+            row = clean(isExplain, res.getString(1));
+            for (int i = 2; i < md.getColumnCount() + 1; i++)
+              row += TAB + clean(isExplain, res.getString(i));
+            msg.append(row + NEWLINE);
+            rowCount++;
+          }
+        }
+        else { // May have been upsert or DDL
+          msg.append(UPDATE_HEADER + NEWLINE +
+            "Rows affected: " + currentStatement.getUpdateCount()
+            + NEWLINE);
+        }
+
+      } finally {
+        try {
+          if (res != null) res.close();
+          getJdbcConnection().commit();
+          currentStatement.close();
+        } finally {
+          currentStatement = null;
+        }
+      }
+
+      return new InterpreterResult(Code.SUCCESS, msg.toString());
+    }
+    catch (SQLException ex) {
+      logger.error("Can not run " + sql, ex);
+      return new InterpreterResult(Code.ERROR, ex.getMessage());
+    }
+  }
+
+  @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(
+        PhoenixInterpreter.class.getName() + this.hashCode());
+  }
+
+  @Override
+  public List<String> completion(String buf, int cursor) {
+    return null;
+  }
+
+  public Connection getJdbcConnection() {
+    return jdbcConnection;
+  }
+
+  public int getMaxResult() {
+    return maxResult;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7ba743ee/phoenix/src/test/java/org/apache/zeppelin/phoenix/PhoenixInterpreterTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix/src/test/java/org/apache/zeppelin/phoenix/PhoenixInterpreterTest.java 
b/phoenix/src/test/java/org/apache/zeppelin/phoenix/PhoenixInterpreterTest.java
new file mode 100644
index 0000000..9270d4b
--- /dev/null
+++ 
b/phoenix/src/test/java/org/apache/zeppelin/phoenix/PhoenixInterpreterTest.java
@@ -0,0 +1,226 @@
+/**
+ * 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.phoenix;
+
+import static org.apache.zeppelin.phoenix.PhoenixInterpreter.*;
+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;
+
+/**
+ * Phoenix interpreter unit tests
+ */
+public class PhoenixInterpreterTest extends BasicJDBCTestCaseAdapter {
+  private PhoenixInterpreter phoenixInterpreter = 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(PHOENIX_JDBC_DRIVER_NAME, DEFAULT_JDBC_DRIVER_NAME);
+    properties.put(PHOENIX_JDBC_URL, DEFAULT_JDBC_URL);
+    properties.put(PHOENIX_JDBC_USER, DEFAULT_JDBC_USER);
+    properties.put(PHOENIX_JDBC_PASSWORD, DEFAULT_JDBC_PASSWORD);
+    properties.put(PHOENIX_MAX_RESULT, DEFAULT_MAX_RESULT);
+
+    phoenixInterpreter = spy(new PhoenixInterpreter(properties));
+    when(phoenixInterpreter.getJdbcConnection()).thenReturn(connection);
+  }
+
+  @Test
+  public void testOpenCommandIdempotency() throws SQLException {
+    // Ensure that an attempt to open new connection will clean any remaining 
connections
+    phoenixInterpreter.open();
+    phoenixInterpreter.open();
+    phoenixInterpreter.open();
+
+    verify(phoenixInterpreter, times(3)).open();
+    verify(phoenixInterpreter, times(3)).close();
+  }
+
+  @Test
+  public void testDefaultProperties() throws SQLException {
+
+    PhoenixInterpreter phoenixInterpreter = new PhoenixInterpreter(new 
Properties());
+
+    assertEquals(DEFAULT_JDBC_DRIVER_NAME,
+        phoenixInterpreter.getProperty(PHOENIX_JDBC_DRIVER_NAME));
+    assertEquals(DEFAULT_JDBC_URL, 
phoenixInterpreter.getProperty(PHOENIX_JDBC_URL));
+    assertEquals(DEFAULT_JDBC_USER, 
phoenixInterpreter.getProperty(PHOENIX_JDBC_USER));
+    assertEquals(DEFAULT_JDBC_PASSWORD,
+        phoenixInterpreter.getProperty(PHOENIX_JDBC_PASSWORD));
+    assertEquals(DEFAULT_MAX_RESULT, 
phoenixInterpreter.getProperty(PHOENIX_MAX_RESULT));
+  }
+
+  @Test
+  public void testConnectionClose() throws SQLException {
+
+    PhoenixInterpreter phoenixInterpreter = spy(new PhoenixInterpreter(new 
Properties()));
+
+    when(phoenixInterpreter.getJdbcConnection()).thenReturn(
+        getJDBCMockObjectFactory().getMockConnection());
+
+    phoenixInterpreter.close();
+
+    verifyAllResultSetsClosed();
+    verifyAllStatementsClosed();
+    verifyConnectionClosed();
+  }
+
+  @Test
+  public void testStatementCancel() throws SQLException {
+
+    PhoenixInterpreter phoenixInterpreter = spy(new PhoenixInterpreter(new 
Properties()));
+
+    when(phoenixInterpreter.getJdbcConnection()).thenReturn(
+        getJDBCMockObjectFactory().getMockConnection());
+
+    phoenixInterpreter.cancel(null);
+
+    verifyAllResultSetsClosed();
+    verifyAllStatementsClosed();
+    assertFalse("Cancel operation should not close the connection", 
phoenixInterpreter
+        .getJdbcConnection().isClosed());
+  }
+
+  @Test
+  public void testSelectQuery() throws SQLException {
+
+    when(phoenixInterpreter.getMaxResult()).thenReturn(1000);
+
+    String sqlQuery = "select * from t";
+
+    result.addColumn("col1", new String[] {"val11", "val12"});
+    result.addColumn("col2", new String[] {"val21", "val22"});
+
+    InterpreterResult interpreterResult = 
phoenixInterpreter.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(phoenixInterpreter.getMaxResult()).thenReturn(1);
+
+    String sqlQuery = "select * from t";
+
+    result.addColumn("col1", new String[] {"val11", "val12"});
+    result.addColumn("col2", new String[] {"val21", "val22"});
+
+    InterpreterResult interpreterResult = 
phoenixInterpreter.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(phoenixInterpreter.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 = 
phoenixInterpreter.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(phoenixInterpreter.getMaxResult()).thenReturn(1000);
+
+    String sqlQuery = "explain select * from t";
+
+    result.addColumn("col1", new String[] {"val11", "val12"});
+
+    InterpreterResult interpreterResult = 
phoenixInterpreter.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(phoenixInterpreter.getMaxResult()).thenReturn(1000);
+
+    String sqlQuery = "explain select * from t";
+
+    result.addColumn("co\tl\n1", new String[] {"va\nl11", "va\tl\n12"});
+
+    InterpreterResult interpreterResult = 
phoenixInterpreter.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/7ba743ee/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 1c67f47..79546a9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,6 +91,7 @@
     <module>angular</module>
     <module>shell</module>
     <module>hive</module>
+    <module>phoenix</module>
     <module>geode</module>
     <module>postgresql</module>
     <module>tajo</module>

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7ba743ee/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 2fdaee1..363b154 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
@@ -402,6 +402,7 @@ public class ZeppelinConfiguration extends XMLConfiguration 
{
         + "org.apache.zeppelin.angular.AngularInterpreter,"
         + "org.apache.zeppelin.shell.ShellInterpreter,"
         + "org.apache.zeppelin.hive.HiveInterpreter,"
+        + "org.apache.zeppelin.phoenix.PhoenixInterpreter,"
         + "org.apache.zeppelin.tajo.TajoInterpreter,"
         + "org.apache.zeppelin.flink.FlinkInterpreter,"
         + "org.apache.zeppelin.ignite.IgniteInterpreter,"

Reply via email to