diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template index 9f773d50add..12d571b9c92 100644 --- a/conf/zeppelin-site.xml.template +++ b/conf/zeppelin-site.xml.template @@ -66,7 +66,7 @@ zeppelin.interpreters - 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.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.jdbc.JDBCInterpreter Comma separated interpreter configurations. First interpreter become a default diff --git a/jdbc/COPYING b/jdbc/COPYING new file mode 100644 index 00000000000..78d5b1b8243 --- /dev/null +++ b/jdbc/COPYING @@ -0,0 +1,18 @@ +# +# 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. +# +# @Author Hyungu Roh +# diff --git a/jdbc/pom.xml b/jdbc/pom.xml new file mode 100644 index 00000000000..ab94b41281b --- /dev/null +++ b/jdbc/pom.xml @@ -0,0 +1,140 @@ + + + + + 4.0.0 + + + zeppelin + org.apache.zeppelin + 0.5.0-SNAPSHOT + + + org.apache.zeppelin + zeppelin-jdbc + jar + 0.5.0-SNAPSHOT + Zeppelin: JDBC interpreter + http://zeppelin.incubator.apache.org + + + + ${project.groupId} + zeppelin-interpreter + ${project.version} + provided + + + + org.apache.commons + commons-exec + 1.1 + + + + org.slf4j + slf4j-api + + + + org.slf4j + slf4j-log4j12 + + + + junit + junit + test + + + + mysql + mysql-connector-java + 5.1.35 + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.7 + + true + + + + + maven-enforcer-plugin + 1.3.1 + + + enforce + none + + + + + + maven-dependency-plugin + 2.8 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/../../interpreter/jdbc + false + false + true + runtime + + + + copy-artifact + package + + copy + + + ${project.build.directory}/../../interpreter/jdbc + false + false + true + runtime + + + ${project.groupId} + ${project.artifactId} + ${project.version} + ${project.packaging} + + + + + + + + + + diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/DBConnection.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/DBConnection.java new file mode 100644 index 00000000000..08966990065 --- /dev/null +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/DBConnection.java @@ -0,0 +1,36 @@ +/* + * 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.jdbc; + +import org.apache.zeppelin.interpreter.InterpreterResult; + +/** + * DBConnection + * + * @author Hyungu Roh hyungu.roh@navercorp.com + * + */ + +public interface DBConnection { + void open(); + void close(); + InterpreterResult executeSql(String sql); + void cancel(); +} + diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/DBConnectionFactory.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/DBConnectionFactory.java new file mode 100644 index 00000000000..15f0b466e92 --- /dev/null +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/DBConnectionFactory.java @@ -0,0 +1,76 @@ +/* + * 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.jdbc; + +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.sql.Connection; + +/** + * DBConnectionFactory + * + * @author Hyungu Roh hyungu.roh@navercorp.com + * + */ + +public class DBConnectionFactory { + Logger logger = LoggerFactory.getLogger(DBConnectionFactory.class); + + String jdbc = ""; + String host = ""; + String port = ""; + String user = ""; + String password = ""; + + public DBConnectionFactory(Properties currentProperty) { + for (Object k : currentProperty.keySet()) { + String key = (String) k; + String value = (String) currentProperty.get(key); + + if ( key.equals("jdbc") ) { + this.jdbc = value.toLowerCase(); + } else if ( key.equals("host") ) { + this.host = value; + } else if ( key.equals("password") ) { + this.password = value; + } else if ( key.equals("user") ) { + this.user = value; + } else if ( key.equals("port") ) { + this.port = value; + } else { + logger.info("else key : " + key); + } + } + } + + public DBConnection getDBConnection() { + DBConnection currentConnection; + + // add other jdbc + if ( jdbc.equals("mysql") ) { + currentConnection = new MysqlConnection(host, port, user, password); + } else { + currentConnection = null; + } + + return currentConnection; + } +} + diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java new file mode 100644 index 00000000000..1c0cf4f4ed4 --- /dev/null +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java @@ -0,0 +1,110 @@ +/* + * 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.jdbc; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Properties; +import java.util.Vector; + +import org.apache.commons.lang.StringUtils; +import org.apache.zeppelin.interpreter.Interpreter; +import org.apache.zeppelin.interpreter.InterpreterContext; +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 java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * JDBC interpreter for Zeppelin. + * + * @author Hyungu Roh hyungu.roh@navercorp.com + * + */ + +public class JDBCInterpreter extends Interpreter { + Logger logger = LoggerFactory.getLogger(JDBCInterpreter.class); + int commandTimeOut = 600000; + + static { + Interpreter.register("jdbc", JDBCInterpreter.class.getName()); + } + + DBConnection jdbcConnection; + + public JDBCInterpreter(Properties property) { + super(property); + Properties currentProperty = getProperty(); + DBConnectionFactory DBFactory = new DBConnectionFactory(currentProperty); + this.jdbcConnection = DBFactory.getDBConnection(); + } + + @Override + public void open() { + jdbcConnection.open(); + } + + @Override + public void close() { + jdbcConnection.close(); + } + + @Override + public InterpreterResult interpret(String cmd, InterpreterContext contextInterpreter) { + logger.info("Run SQL command '" + cmd + "'"); + return jdbcConnection.executeSql(cmd); + } + + @Override + public void cancel(InterpreterContext context) { + jdbcConnection.cancel(); + } + + @Override + public FormType getFormType() { + return FormType.SIMPLE; + } + + @Override + public int getProgress(InterpreterContext context) { + return 0; + } + + @Override + public Scheduler getScheduler() { + return SchedulerFactory.singleton().createOrGetFIFOScheduler( + JDBCInterpreter.class.getName() + this.hashCode()); + } + + @Override + public List completion(String buf, int cursor) { + return null; + } + +} diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/MysqlConnection.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/MysqlConnection.java new file mode 100644 index 00000000000..10f6ef88804 --- /dev/null +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/MysqlConnection.java @@ -0,0 +1,155 @@ +/* + * 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.jdbc; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.commons.lang.StringUtils; + +import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.InterpreterResult.Code; + +import java.util.Vector; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * MysqlConnection + * + * @author Hyungu Roh hyungu.roh@navercorp.com + * + */ + +public class MysqlConnection implements DBConnection { + Logger logger = LoggerFactory.getLogger(MysqlConnection.class); + + String host; + String port; + String user; + String passwd; + + public MysqlConnection(String host, String port, String user, String passwd) { + this.host = host; + this.port = port; + this.user = user; + this.passwd = passwd; + } + + Connection mysqlConnection; + Exception exceptionOnConnect; + Statement currentStatement; + ResultSet resultSet; + + @Override + public void open() { + String driver = "com.mysql.jdbc.Driver"; + String url = "jdbc:mysql://"; + url += host; + url += port != "" ? ":" + port : ""; + url += "/?user=" + user; + + try { + Class.forName(driver); + mysqlConnection = DriverManager.getConnection(url, user, passwd); + } catch ( ClassNotFoundException | SQLException e ) { + logger.error("Can not open connection", e); + exceptionOnConnect = e; + } + } + + @Override + public void close() { + try { + mysqlConnection.close(); + currentStatement.close(); + resultSet.close(); + } catch ( SQLException e ) { + logger.error("Can not close connection", e); + } + + mysqlConnection = null; + exceptionOnConnect = null; + currentStatement = null; + resultSet = null; + } + + @Override + public InterpreterResult executeSql(String sql) { + try { + if (exceptionOnConnect != null) { + return new InterpreterResult(Code.ERROR, exceptionOnConnect.getMessage()); + } + currentStatement = mysqlConnection.createStatement(); + StringBuilder msg = null; + if (StringUtils.containsIgnoreCase(sql, "EXPLAIN ")) { + //return the explain as text, make this visual explain later + msg = new StringBuilder(); + } + else { + msg = new StringBuilder("%table "); + } + resultSet = currentStatement.executeQuery(sql); + try { + ResultSetMetaData md = resultSet.getMetaData(); + for (int i = 1; i < md.getColumnCount() + 1; i++) { + if (i == 1) { + msg.append(md.getColumnName(i)); + } else { + msg.append("\t" + md.getColumnName(i)); + } + } + msg.append("\n"); + while (resultSet.next()) { + for (int i = 1; i < md.getColumnCount() + 1; i++) { + msg.append(resultSet.getString(i) + "\t"); + } + msg.append("\n"); + } + } catch ( NullPointerException e ) { + + } + + InterpreterResult rett = new InterpreterResult(Code.SUCCESS, msg.toString()); + return rett; + } + catch (SQLException ex) { + logger.error("Can not run " + sql, ex); + return new InterpreterResult(Code.ERROR, ex.getMessage()); + } + } + + @Override + public void cancel() { + if (currentStatement != null) { + try { + currentStatement.cancel(); + } + catch (SQLException ex) { + } + finally { + currentStatement = null; + } + } + } +} + diff --git a/pom.xml b/pom.xml index 81fd00ecb40..d7f9424241f 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,7 @@ shell hive tajo + jdbc zeppelin-web zeppelin-server zeppelin-distribution 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 bbf46fc88d4..475b9dbe622 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 @@ -389,7 +389,8 @@ public static enum ConfVars { + "org.apache.zeppelin.angular.AngularInterpreter," + "org.apache.zeppelin.shell.ShellInterpreter," + "org.apache.zeppelin.hive.HiveInterpreter," - + "org.apache.zeppelin.tajo.TajoInterpreter"), + + "org.apache.zeppelin.tajo.TajoInterpreter," + + "org.apache.zeppelin.jdbc.JDBCInterpreter"), ZEPPELIN_INTERPRETER_DIR("zeppelin.interpreter.dir", "interpreter"), ZEPPELIN_ENCODING("zeppelin.encoding", "UTF-8"), ZEPPELIN_NOTEBOOK_DIR("zeppelin.notebook.dir", "notebook"),