zeppelin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jongy...@apache.org
Subject zeppelin git commit: [ZEPPELIN-2538] JDBC completer improvements for work with large meta
Date Sun, 04 Jun 2017 16:42:15 GMT
Repository: zeppelin
Updated Branches:
  refs/heads/master 8fbcc4543 -> 524d5f2a3


[ZEPPELIN-2538] JDBC completer improvements for work with large meta

### What is this PR for?
There are some problems if meta is large (few schemas, each schema contains 500+ tables etc.).
Problems:

1. loading is very long
2. each update takes one connection if updates are long, the situation may arise that the entire pool will be busy
3. no cache
This PR solves these problems. Added cache and access by full path (schema.table, schema.table.column) + protection for release the connections

### What type of PR is it?
Improvement

### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-2538

### Screenshots (if appropriate)
![peek 2017-05-15 15-03](https://cloud.githubusercontent.com/assets/25951039/26054252/4cef6980-3985-11e7-9719-e6138eb777f6.gif)

### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? no

Author: Tinkoff DWH <tinkoff.dwh@gmail.com>

Closes #2343 from tinkoff-dwh/ZEPPELIN-2538 and squashes the following commits:

0991c6ab [Tinkoff DWH] [ZEPPELIN-2538] small improvement
e770d261 [Tinkoff DWH] [ZEPPELIN-2538] update description
a5788743 [Tinkoff DWH] [ZEPPELIN-2538] protection long download
f999488b [Tinkoff DWH] Merge remote-tracking branch 'upstream/master' into ZEPPELIN-2538
f26ab5da [Tinkoff DWH] [ZEPPELIN-2538] fix tests
d600fa16 [Tinkoff DWH] [ZEPPELIN-2538] rewrite sql completer to work with large data


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

Branch: refs/heads/master
Commit: 524d5f2a3994b88d073f79c0d1ead0f23b0786ff
Parents: 8fbcc45
Author: Tinkoff DWH <tinkoff.dwh@gmail.com>
Authored: Sat May 20 22:38:11 2017 +0500
Committer: Jongyoul Lee <jongyoul@apache.org>
Committed: Mon Jun 5 01:42:21 2017 +0900

----------------------------------------------------------------------
 docs/interpreter/jdbc.md                        |   5 +
 .../apache/zeppelin/jdbc/JDBCInterpreter.java   |  59 ++-
 .../org/apache/zeppelin/jdbc/SqlCompleter.java  | 459 ++++++++++---------
 .../src/main/resources/interpreter-setting.json |   6 +
 .../zeppelin/jdbc/JDBCInterpreterTest.java      |   6 +-
 .../apache/zeppelin/jdbc/SqlCompleterTest.java  |  76 ++-
 .../zeppelin/completer/CachedCompleter.java     |  44 ++
 .../zeppelin/completer/StringsCompleter.java    |   5 +-
 8 files changed, 386 insertions(+), 274 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/524d5f2a/docs/interpreter/jdbc.md
----------------------------------------------------------------------
diff --git a/docs/interpreter/jdbc.md b/docs/interpreter/jdbc.md
index b7ac45a..9a4ffc8 100644
--- a/docs/interpreter/jdbc.md
+++ b/docs/interpreter/jdbc.md
@@ -128,6 +128,11 @@ The JDBC interpreter properties are defined by default like below.
     <td></td>
     <td>–°omma separated schema (schema = catalog = database) filters to get metadata for completions. Supports '%' symbol is equivalent to any set of characters. (ex. prod_v_%,public%,info)</td>
   </tr>
+  <tr>
+    <td>default.completer.ttlInSeconds</td>
+    <td>120</td>
+    <td>Time to live sql completer in seconds (-1 to update everytime, 0 to disable update)</td>
+  </tr>
 </table>
 
 If you want to connect other databases such as `Mysql`, `Redshift` and `Hive`, you need to edit the property values.

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/524d5f2a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java
index b75d8b8..c5c00e8 100644
--- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java
+++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java
@@ -28,6 +28,9 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Properties;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.dbcp2.ConnectionFactory;
 import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
@@ -101,6 +104,8 @@ public class JDBCInterpreter extends Interpreter {
   static final String PASSWORD_KEY = "password";
   static final String PRECODE_KEY = "precode";
   static final String COMPLETER_SCHEMA_FILTERS_KEY = "completer.schemaFilters";
+  static final String COMPLETER_TTL_KEY = "completer.ttlInSeconds";
+  static final String DEFAULT_COMPLETER_TTL = "120";
   static final String JDBC_JCEKS_FILE = "jceks.file";
   static final String JDBC_JCEKS_CREDENTIAL_KEY = "jceks.credentialKey";
   static final String PRECODE_KEY_TEMPLATE = "%s.precode";
@@ -128,6 +133,7 @@ public class JDBCInterpreter extends Interpreter {
 
   private final HashMap<String, Properties> basePropretiesMap;
   private final HashMap<String, JDBCUserConfigurations> jdbcUserConfigurationsMap;
+  private final HashMap<String, SqlCompleter> sqlCompletersMap;
 
   private int maxLineResults;
 
@@ -135,6 +141,7 @@ public class JDBCInterpreter extends Interpreter {
     super(property);
     jdbcUserConfigurationsMap = new HashMap<>();
     basePropretiesMap = new HashMap<>();
+    sqlCompletersMap = new HashMap<>();
     maxLineResults = MAX_LINE_DEFAULT;
   }
 
@@ -188,11 +195,43 @@ public class JDBCInterpreter extends Interpreter {
     }
   }
 
-  private SqlCompleter createSqlCompleter(Connection jdbcConnection, String propertyKey) {
+  private SqlCompleter createOrUpdateSqlCompleter(SqlCompleter sqlCompleter,
+      final Connection connection, String propertyKey, final String buf, final int cursor) {
     String schemaFiltersKey = String.format("%s.%s", propertyKey, COMPLETER_SCHEMA_FILTERS_KEY);
-    String filters = getProperty(schemaFiltersKey);
-    SqlCompleter completer = new SqlCompleter();
-    completer.initFromConnection(jdbcConnection, filters);
+    String sqlCompleterTtlKey = String.format("%s.%s", propertyKey, COMPLETER_TTL_KEY);
+    final String schemaFiltersString = getProperty(schemaFiltersKey);
+    int ttlInSeconds = Integer.valueOf(
+        StringUtils.defaultIfEmpty(getProperty(sqlCompleterTtlKey), DEFAULT_COMPLETER_TTL)
+    );
+    final SqlCompleter completer;
+    if (sqlCompleter == null) {
+      completer = new SqlCompleter(ttlInSeconds);
+    } else {
+      completer = sqlCompleter;
+    }
+    ExecutorService executorService = Executors.newFixedThreadPool(1);
+    executorService.execute(new Runnable() {
+      @Override
+      public void run() {
+        completer.createOrUpdateFromConnection(connection, schemaFiltersString, buf, cursor);
+      }
+    });
+
+    executorService.shutdown();
+
+    try {
+      // protection to release connection
+      executorService.awaitTermination(3, TimeUnit.SECONDS);
+    } catch (InterruptedException e) {
+      logger.warn("Completion timeout", e);
+      if (connection != null) {
+        try {
+          connection.close();
+        } catch (SQLException e1) {
+          logger.warn("Error close connection", e1);
+        }
+      }
+    }
     return completer;
   }
 
@@ -787,6 +826,10 @@ public class JDBCInterpreter extends Interpreter {
       InterpreterContext interpreterContext) {
     List<InterpreterCompletion> candidates = new ArrayList<>();
     String propertyKey = getPropertyKey(buf);
+    String sqlCompleterKey =
+        String.format("%s.%s", interpreterContext.getAuthenticationInfo().getUser(), propertyKey);
+    SqlCompleter sqlCompleter = sqlCompletersMap.get(sqlCompleterKey);
+
     Connection connection = null;
     try {
       if (interpreterContext != null) {
@@ -796,11 +839,9 @@ public class JDBCInterpreter extends Interpreter {
       logger.warn("SQLCompleter will created without use connection");
     }
 
-    SqlCompleter sqlCompleter = createSqlCompleter(connection, propertyKey);
-
-    if (sqlCompleter != null) {
-      sqlCompleter.complete(buf, cursor - 1, candidates);
-    }
+    sqlCompleter = createOrUpdateSqlCompleter(sqlCompleter, connection, propertyKey, buf, cursor);
+    sqlCompletersMap.put(sqlCompleterKey, sqlCompleter);
+    sqlCompleter.complete(buf, cursor, candidates);
 
     return candidates;
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/524d5f2a/jdbc/src/main/java/org/apache/zeppelin/jdbc/SqlCompleter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/SqlCompleter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/SqlCompleter.java
index 704ec59..46cc4bd 100644
--- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/SqlCompleter.java
+++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/SqlCompleter.java
@@ -24,6 +24,7 @@ import java.util.regex.Pattern;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.math.NumberUtils;
+import org.apache.zeppelin.completer.CachedCompleter;
 import org.apache.zeppelin.completer.CompletionType;
 import org.apache.zeppelin.completer.StringsCompleter;
 import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
@@ -33,8 +34,6 @@ import org.slf4j.LoggerFactory;
 import jline.console.completer.ArgumentCompleter.ArgumentList;
 import jline.console.completer.ArgumentCompleter.WhitespaceArgumentDelimiter;
 
-import static org.apache.commons.lang.StringUtils.isBlank;
-
 /**
  * SQL auto complete functionality for the JdbcInterpreter.
  */
@@ -42,6 +41,7 @@ public class SqlCompleter {
 
   private static Logger logger = LoggerFactory.getLogger(SqlCompleter.class);
 
+
   /**
    * Delimiter that can split SQL statement in keyword list
    */
@@ -59,23 +59,30 @@ public class SqlCompleter {
   /**
    * Schema completer
    */
-  private StringsCompleter schemasCompleter = new StringsCompleter();
+  private CachedCompleter schemasCompleter;
 
   /**
    * Contain different completer with table list for every schema name
    */
-  private Map<String, StringsCompleter> tablesCompleters = new HashMap<>();
+  private Map<String, CachedCompleter> tablesCompleters = new HashMap<>();
 
   /**
    * Contains different completer with column list for every table name
    * Table names store as schema_name.table_name
    */
-  private Map<String, StringsCompleter> columnsCompleters = new HashMap<>();
+  private Map<String, CachedCompleter> columnsCompleters = new HashMap<>();
 
   /**
    * Completer for sql keywords
    */
-  private StringsCompleter keywordCompleter = new StringsCompleter();
+  private CachedCompleter keywordCompleter;
+
+  private int ttlInSeconds;
+
+
+  public SqlCompleter(int ttlInSeconds) {
+    this.ttlInSeconds = ttlInSeconds;
+  }
 
   public int complete(String buffer, int cursor, List<InterpreterCompletion> candidates) {
 
@@ -95,25 +102,9 @@ public class SqlCompleter {
       argumentPosition = argumentList.getArgumentPosition();
     }
 
-    boolean isColumnAllowed = true;
-    if (buffer.length() > 0) {
-      String beforeCursorBuffer = buffer.substring(0,
-          Math.min(cursor, buffer.length())).toUpperCase();
-      // check what sql is and where cursor is to allow column completion or not
-      if (beforeCursorBuffer.contains("SELECT ") && beforeCursorBuffer.contains(" FROM ")
-          && !beforeCursorBuffer.contains(" WHERE "))
-        isColumnAllowed = false;
-    }
-
     int complete = completeName(cursorArgument, argumentPosition, candidates,
-            findAliasesInSQL(argumentList.getArguments()), isColumnAllowed);
+            findAliasesInSQL(argumentList.getArguments()));
 
-    if (candidates.size() == 1) {
-      InterpreterCompletion interpreterCompletion = candidates.get(0);
-      interpreterCompletion.setName(interpreterCompletion.getName() + " ");
-      interpreterCompletion.setValue(interpreterCompletion.getValue() + " ");
-      candidates.set(0, interpreterCompletion);
-    }
     logger.debug("complete:" + complete + ", size:" + candidates.size());
     return complete;
   }
@@ -132,6 +123,7 @@ public class SqlCompleter {
     Set<String> res = new HashSet<>();
     try {
       ResultSet schemas = meta.getSchemas();
+
       try {
         while (schemas.next()) {
           String schemaName = schemas.getString("TABLE_SCHEM");
@@ -185,58 +177,40 @@ public class SqlCompleter {
     return res;
   }
 
+
+  private static void fillTableNames(String schema, DatabaseMetaData meta, Set<String> tables) {
+    try (ResultSet tbls = meta.getTables(schema, schema, "%", null)) {
+      while (tbls.next()) {
+        String table = tbls.getString("TABLE_NAME");
+        tables.add(table);
+      }
+    } catch (Throwable t) {
+      logger.error("Failed to retrieve the table name", t);
+    }
+  }
+
   /**
    * Fill two map with list of tables and list of columns
    *
-   * @param catalogName name of a catalog
-   * @param meta metadata from connection to database
-   * @param schemaFilter a schema name pattern; must match the schema name
-   *        as it is stored in the database; "" retrieves those without a schema;
-   *        <code>null</code> means that the schema name should not be used to narrow
-   *        the search; supports '%'; for example "prod_v_%"
-   * @param tables function fills this map, for every schema name adds
-   *        set of table names within the schema
-   * @param columns function fills this map, for every table name adds set
+   * @param schema name of a scheme
+   * @param table name of a table
+   * @param meta meta metadata from connection to database
+   * @param columns function fills this set, for every table name adds set
    *        of columns within the table; table name is in format schema_name.table_name
    */
-  private static void fillTableAndColumnNames(String catalogName, DatabaseMetaData meta,
-                                              String schemaFilter,
-                                              Map<String, Set<String>> tables,
-                                              Map<String, Set<String>> columns)  {
-    try {
-      ResultSet cols = meta.getColumns(catalogName, StringUtils.EMPTY, "%", "%");
-      try {
-        while (cols.next()) {
-          String schema = cols.getString("TABLE_SCHEM");
-          if (schema == null) {
-            schema = cols.getString("TABLE_CAT");
-          }
-          if (!schemaFilter.equals("") && !schema.matches(schemaFilter.replace("%", ".*?"))) {
-            continue;
-          }
-          String table = cols.getString("TABLE_NAME");
-          String column = cols.getString("COLUMN_NAME");
-          if (!isBlank(table)) {
-            String schemaTable = schema + "." + table;
-            if (!columns.containsKey(schemaTable)) {
-              columns.put(schemaTable, new HashSet<String>());
-            }
-            columns.get(schemaTable).add(column);
-            if (!tables.containsKey(schema)) {
-              tables.put(schema, new HashSet<String>());
-            }
-            tables.get(schema).add(table);
-          }
-        }
-      } finally {
-        cols.close();
+  private static void fillColumnNames(String schema, String table, DatabaseMetaData meta,
+      Set<String> columns) {
+    try (ResultSet cols = meta.getColumns(schema, schema, table, "%")) {
+      while (cols.next()) {
+        String column = cols.getString("COLUMN_NAME");
+        columns.add(column);
       }
     } catch (Throwable t) {
       logger.error("Failed to retrieve the column name", t);
     }
   }
 
-  public static Set<String> getSqlKeywordsCompletions(Connection connection) throws IOException,
+  public static Set<String> getSqlKeywordsCompletions(DatabaseMetaData meta) throws IOException,
           SQLException {
 
     // Add the default SQL completions
@@ -246,12 +220,11 @@ public class SqlCompleter {
 
     Set<String> completions = new TreeSet<>();
 
-    if (null != connection) {
-      DatabaseMetaData metaData = connection.getMetaData();
+    if (null != meta) {
 
       // Add the driver specific SQL completions
       String driverSpecificKeywords =
-              "/" + metaData.getDriverName().replace(" ", "-").toLowerCase() + "-sql.keywords";
+              "/" + meta.getDriverName().replace(" ", "-").toLowerCase() + "-sql.keywords";
       logger.info("JDBC DriverName:" + driverSpecificKeywords);
       try {
         if (SqlCompleter.class.getResource(driverSpecificKeywords) != null) {
@@ -269,27 +242,27 @@ public class SqlCompleter {
 
       // Add the keywords from the current JDBC connection
       try {
-        keywords += "," + metaData.getSQLKeywords();
+        keywords += "," + meta.getSQLKeywords();
       } catch (Exception e) {
         logger.debug("fail to get SQL key words from database metadata: " + e, e);
       }
       try {
-        keywords += "," + metaData.getStringFunctions();
+        keywords += "," + meta.getStringFunctions();
       } catch (Exception e) {
         logger.debug("fail to get string function names from database metadata: " + e, e);
       }
       try {
-        keywords += "," + metaData.getNumericFunctions();
+        keywords += "," + meta.getNumericFunctions();
       } catch (Exception e) {
         logger.debug("fail to get numeric function names from database metadata: " + e, e);
       }
       try {
-        keywords += "," + metaData.getSystemFunctions();
+        keywords += "," + meta.getSystemFunctions();
       } catch (Exception e) {
         logger.debug("fail to get system function names from database metadata: " + e, e);
       }
       try {
-        keywords += "," + metaData.getTimeDateFunctions();
+        keywords += "," + meta.getTimeDateFunctions();
       } catch (Exception e) {
         logger.debug("fail to get time date function names from database metadata: " + e, e);
       }
@@ -308,94 +281,100 @@ public class SqlCompleter {
   }
 
   /**
-   * Initializes local schema completers from list of schema names
-   *
-   * @param schemas set of schema names
-   */
-  private void initSchemas(Set<String> schemas) {
-    schemasCompleter = new StringsCompleter(new TreeSet<>(schemas));
-  }
-
-  /**
-   * Initializes local table completers from list of table name
-   *
-   * @param tables for every schema name there is a set of table names within the schema
-   */
-  private void initTables(Map<String, Set<String>> tables) {
-    tablesCompleters.clear();
-    for (Map.Entry<String, Set<String>> entry : tables.entrySet()) {
-      tablesCompleters.put(entry.getKey(), new StringsCompleter(new TreeSet<>(entry.getValue())));
-    }
-  }
-
-  /**
-   * Initializes local column completers from list of column names
-   *
-   * @param columns for every table name there is a set of columns within the table;
-   *        table name is in format schema_name.table_name
-   */
-  private void initColumns(Map<String, Set<String>> columns) {
-    columnsCompleters.clear();
-    for (Map.Entry<String, Set<String>> entry : columns.entrySet()) {
-      columnsCompleters.put(entry.getKey(), new StringsCompleter(new TreeSet<>(entry.getValue())));
-    }
-  }
-
-  /**
-   * Initializes all local completers
-   *
-   * @param schemas set of schema names
-   * @param tables for every schema name there is a set of table names within the schema
-   * @param columns for every table name there is a set of columns within the table;
-   *        table name is in format schema_name.table_name
-   * @param keywords set with sql keywords
-   */
-  public void init(Set<String> schemas, Map<String, Set<String>> tables,
-                   Map<String, Set<String>> columns, Set<String> keywords) {
-    initSchemas(schemas);
-    initTables(tables);
-    initColumns(columns);
-    keywordCompleter = new StringsCompleter(keywords);
-  }
-
-  /**
    * Initializes all local completers from database connection
    *
    * @param connection database connection
-   * @param schemaFiltersString a comma separated schema name patterns; supports '%'  symbol;
-   * for example "prod_v_%,prod_t_%"
+   * @param schemaFiltersString a comma separated schema name patterns, supports '%'  symbol;
+   *        for example "prod_v_%,prod_t_%"
    */
-  public void initFromConnection(Connection connection, String schemaFiltersString) {
-    if (schemaFiltersString == null) {
-      schemaFiltersString = StringUtils.EMPTY;
-    }
-    List<String> schemaFilters = Arrays.asList(schemaFiltersString.split(","));
-
+  public void createOrUpdateFromConnection(Connection connection, String schemaFiltersString,
+      String buffer, int cursor) {
     try (Connection c = connection) {
-      Map<String, Set<String>> tables = new HashMap<>();
-      Map<String, Set<String>> columns = new HashMap<>();
+      if (schemaFiltersString == null) {
+        schemaFiltersString = StringUtils.EMPTY;
+      }
+      List<String> schemaFilters = Arrays.asList(schemaFiltersString.split(","));
+      CursorArgument cursorArgument = parseCursorArgument(buffer, cursor);
+
+      Set<String> tables = new HashSet<>();
+      Set<String> columns = new HashSet<>();
       Set<String> schemas = new HashSet<>();
       Set<String> catalogs = new HashSet<>();
-      Set<String> keywords = getSqlKeywordsCompletions(connection);
-      if (connection != null) {
-        schemas = getSchemaNames(connection.getMetaData(), schemaFilters);
-        catalogs = getCatalogNames(connection.getMetaData(), schemaFilters);
-        if (schemas.size() == 0) {
-          schemas.addAll(catalogs);
+      Set<String> keywords = new HashSet<>();
+
+      if (c != null) {
+        DatabaseMetaData databaseMetaData = c.getMetaData();
+        if (keywordCompleter == null || keywordCompleter.getCompleter() == null
+            || keywordCompleter.isExpired()) {
+          keywords = getSqlKeywordsCompletions(databaseMetaData);
+          initKeywords(keywords);
         }
-        for (String schema : schemas) {
-          for (String schemaFilter : schemaFilters) {
-            fillTableAndColumnNames(schema, connection.getMetaData(), schemaFilter, tables,
-                columns);
+        if (cursorArgument.needLoadSchemas() &&
+            (schemasCompleter == null || schemasCompleter.getCompleter() == null
+            || schemasCompleter.isExpired())) {
+          schemas = getSchemaNames(databaseMetaData, schemaFilters);
+          catalogs = getCatalogNames(databaseMetaData, schemaFilters);
+
+          if (schemas.size() == 0) {
+            schemas.addAll(catalogs);
           }
+
+          initSchemas(schemas);
+        }
+
+        CachedCompleter tablesCompleter = tablesCompleters.get(cursorArgument.getSchema());
+        if (cursorArgument.needLoadTables() &&
+            (tablesCompleter == null || tablesCompleter.isExpired())) {
+          fillTableNames(cursorArgument.getSchema(), databaseMetaData, tables);
+          initTables(cursorArgument.getSchema(), tables);
+        }
+
+        String schemaTable =
+            String.format("%s.%s", cursorArgument.getSchema(), cursorArgument.getTable());
+        CachedCompleter columnsCompleter = columnsCompleters.get(schemaTable);
+
+        if (cursorArgument.needLoadColumns() &&
+            (columnsCompleter == null || columnsCompleter.isExpired())) {
+          fillColumnNames(cursorArgument.getSchema(), cursorArgument.getTable(), databaseMetaData,
+              columns);
+          initColumns(schemaTable, columns);
         }
+
+        logger.info("Completer initialized with " + schemas.size() + " schemas, " +
+            columns.size() + " tables and " + keywords.size() + " keywords");
       }
-      init(schemas, tables, columns, keywords);
-      logger.info("Completer initialized with " + schemas.size() + " schemas, " +
-              columns.size() + " tables and " + keywords.size() + " keywords");
 
     } catch (SQLException | IOException e) {
-      logger.error("Failed to update the metadata conmpletions", e);
+      logger.error("Failed to update the metadata completions", e);
+    }
+  }
+
+
+
+  public void initKeywords(Set<String> keywords) {
+    if (keywords != null && !keywords.isEmpty()) {
+      keywordCompleter = new CachedCompleter(new StringsCompleter(keywords), 0);
+    }
+  }
+
+  public void initSchemas(Set<String> schemas) {
+    if (schemas != null && !schemas.isEmpty()) {
+      schemasCompleter = new CachedCompleter(
+          new StringsCompleter(new TreeSet<>(schemas)), ttlInSeconds);
+    }
+  }
+
+  public void initTables(String schema, Set<String> tables) {
+    if (tables != null && !tables.isEmpty()) {
+      tablesCompleters.put(schema, new CachedCompleter(
+          new StringsCompleter(new TreeSet<>(tables)), ttlInSeconds));
+    }
+  }
+
+  public void initColumns(String schemaTable, Set<String> columns) {
+    if (columns != null && !columns.isEmpty()) {
+      columnsCompleters.put(schemaTable,
+          new CachedCompleter(new StringsCompleter(columns), ttlInSeconds));
     }
   }
 
@@ -422,7 +401,7 @@ public class SqlCompleter {
    * @return -1 in case of no candidates found, 0 otherwise
    */
   private int completeKeyword(String buffer, int cursor, List<CharSequence> candidates) {
-    return keywordCompleter.complete(buffer, cursor, candidates);
+    return keywordCompleter.getCompleter().complete(buffer, cursor, candidates);
   }
 
   /**
@@ -431,7 +410,7 @@ public class SqlCompleter {
    * @return -1 in case of no candidates found, 0 otherwise
    */
   private int completeSchema(String buffer, int cursor, List<CharSequence> candidates) {
-    return schemasCompleter.complete(buffer, cursor, candidates);
+    return schemasCompleter.getCompleter().complete(buffer, cursor, candidates);
   }
 
   /**
@@ -441,21 +420,12 @@ public class SqlCompleter {
    */
   private int completeTable(String schema, String buffer, int cursor,
                             List<CharSequence> candidates) {
-    if (schema == null) {
-      int res = -1;
-      Set<CharSequence> candidatesSet = new HashSet<>();
-      for (StringsCompleter stringsCompleter : tablesCompleters.values()) {
-        int resTable = stringsCompleter.complete(buffer, cursor, candidatesSet);
-        res = Math.max(res, resTable);
-      }
-      candidates.addAll(candidatesSet);
-      return res;
-    }
     // Wrong schema
-    if (!tablesCompleters.containsKey(schema) && schema != null)
+    if (schema == null || !tablesCompleters.containsKey(schema))
       return -1;
-    else
-      return tablesCompleters.get(schema).complete(buffer, cursor, candidates);
+    else {
+      return tablesCompleters.get(schema).getCompleter().complete(buffer, cursor, candidates);
+    }
   }
 
   /**
@@ -465,22 +435,12 @@ public class SqlCompleter {
    */
   private int completeColumn(String schema, String table, String buffer, int cursor,
                              List<CharSequence> candidates) {
-    if (table == null && schema == null) {
-      int res = -1;
-      Set<CharSequence> candidatesSet = new HashSet<>();
-      for (StringsCompleter stringsCompleter : columnsCompleters.values()) {
-        int resColumn = stringsCompleter.complete(buffer, cursor, candidatesSet);
-        res = Math.max(res, resColumn);
-      }
-      candidates.addAll(candidatesSet);
-      return res;
-    }
     // Wrong schema or wrong table
-    if (!tablesCompleters.containsKey(schema) ||
-        !columnsCompleters.containsKey(schema + "." + table)) {
+    if (schema == null || table == null || !columnsCompleters.containsKey(schema + "." + table)) {
       return -1;
     } else {
-      return columnsCompleters.get(schema + "." + table).complete(buffer, cursor, candidates);
+      return columnsCompleters.get(schema + "." + table).getCompleter()
+          .complete(buffer, cursor, candidates);
     }
   }
 
@@ -489,74 +449,55 @@ public class SqlCompleter {
    * a schema, a table of a column or a keyword
    *
    * @param aliases for every alias contains table name in format schema_name.table_name
-   * @param isColumnAllowed if false the function will not search and complete columns
    * @return -1 in case of no candidates found, 0 otherwise
    */
   public int completeName(String buffer, int cursor, List<InterpreterCompletion> candidates,
-                          Map<String, String> aliases, boolean isColumnAllowed) {
+                          Map<String, String> aliases) {
+    CursorArgument cursorArgument = parseCursorArgument(buffer, cursor);
 
-    // points divide the name to the schema, table and column - find them
-    int pointPos1 = -1;
-    int pointPos2 = -1;
-
-    if (StringUtils.isNotEmpty(buffer)) {
-      if (buffer.length() > cursor) {
-        buffer = buffer.substring(0, cursor + 1);
-      }
-      pointPos1 = buffer.indexOf('.');
-      pointPos2 = buffer.indexOf('.', pointPos1 + 1);
-    }
     // find schema and table name if they are
     String schema;
     String table;
     String column;
 
-    if (pointPos1 == -1) {             // process all
+    if (cursorArgument.getSchema() == null) {             // process all
       List<CharSequence> keywordsCandidates = new ArrayList();
       List<CharSequence> schemaCandidates = new ArrayList<>();
-      List<CharSequence> tableCandidates = new ArrayList<>();
-      List<CharSequence> columnCandidates = new ArrayList<>();
       int keywordsRes = completeKeyword(buffer, cursor, keywordsCandidates);
       int schemaRes = completeSchema(buffer, cursor, schemaCandidates);
-      int tableRes = completeTable(null, buffer, cursor, tableCandidates);
-      int columnRes = -1;
-      if (isColumnAllowed) {
-        columnRes = completeColumn(null, null, buffer, cursor, columnCandidates);
-      }
       addCompletions(candidates, keywordsCandidates, CompletionType.keyword.name());
       addCompletions(candidates, schemaCandidates, CompletionType.schema.name());
-      addCompletions(candidates, tableCandidates, CompletionType.table.name());
-      addCompletions(candidates, columnCandidates, CompletionType.column.name());
-      return NumberUtils.max(new int[]{keywordsRes, schemaRes, tableRes, columnRes});
+      return NumberUtils.max(new int[]{keywordsRes, schemaRes});
     } else {
-      schema = buffer.substring(0, pointPos1);
+      schema = cursorArgument.getSchema();
       if (aliases.containsKey(schema)) {  // process alias case
         String alias = aliases.get(schema);
         int pointPos = alias.indexOf('.');
         schema = alias.substring(0, pointPos);
         table = alias.substring(pointPos + 1);
-        column = buffer.substring(pointPos1 + 1);
-      } else if (pointPos2 == -1) {        // process schema.table case
+        column = cursorArgument.getColumn();
+        List<CharSequence> columnCandidates = new ArrayList();
+        int columnRes = completeColumn(schema, table, column, cursorArgument.getCursorPosition(),
+            columnCandidates);
+        addCompletions(candidates, columnCandidates, CompletionType.column.name());
+        // process schema.table case
+      } else if (cursorArgument.getTable() != null && cursorArgument.getColumn() == null) {
         List<CharSequence> tableCandidates = new ArrayList();
-        table = buffer.substring(pointPos1 + 1);
-        int tableRes = completeTable(schema, table, cursor - pointPos1 - 1, tableCandidates);
+        table = cursorArgument.getTable();
+        int tableRes = completeTable(schema, table, cursorArgument.getCursorPosition(),
+            tableCandidates);
         addCompletions(candidates, tableCandidates, CompletionType.table.name());
         return tableRes;
       } else {
-        table = buffer.substring(pointPos1 + 1, pointPos2);
-        column = buffer.substring(pointPos2 + 1);
+        List<CharSequence> columnCandidates = new ArrayList();
+        table = cursorArgument.getTable();
+        column = cursorArgument.getColumn();
+        int columnRes = completeColumn(schema, table, column, cursorArgument.getCursorPosition(),
+            columnCandidates);
+        addCompletions(candidates, columnCandidates, CompletionType.column.name());
       }
     }
 
-    // here in case of column
-    if (table != null && isColumnAllowed) {
-      List<CharSequence> columnCandidates = new ArrayList();
-      int columnRes = completeColumn(schema, table, column, cursor - pointPos2 - 1,
-          columnCandidates);
-      addCompletions(candidates, columnCandidates, CompletionType.column.name());
-      return columnRes;
-    }
-
     return -1;
   }
 
@@ -572,4 +513,92 @@ public class SqlCompleter {
           candidate.toString(), meta));
     }
   }
+
+  private CursorArgument parseCursorArgument(String buffer, int cursor) {
+    CursorArgument result = new CursorArgument();
+    if (buffer != null && buffer.length() >= cursor) {
+      String buf = buffer.substring(0, cursor);
+      if (StringUtils.isNotBlank(buf)) {
+        ArgumentList argumentList = sqlDelimiter.delimit(buf, cursor);
+        String cursorArgument = argumentList.getCursorArgument();
+        if (cursorArgument != null) {
+          int pointPos1 = cursorArgument.indexOf('.');
+          int pointPos2 = cursorArgument.indexOf('.', pointPos1 + 1);
+          if (pointPos1 > -1) {
+            result.setSchema(cursorArgument.substring(0, pointPos1).trim());
+            if (pointPos2 > -1) {
+              result.setTable(cursorArgument.substring(pointPos1 + 1, pointPos2));
+              result.setColumn(cursorArgument.substring(pointPos2 + 1));
+              result.setCursorPosition(cursor - pointPos2 - 1);
+            } else {
+              result.setTable(cursorArgument.substring(pointPos1 + 1));
+              result.setCursorPosition(cursor - pointPos1 - 1);
+            }
+          }
+        }
+      }
+    }
+
+    return result;
+  }
+
+  private class CursorArgument {
+    private String schema;
+    private String table;
+    private String column;
+    private int cursorPosition;
+
+    public String getSchema() {
+      return schema;
+    }
+
+    public void setSchema(String schema) {
+      this.schema = schema;
+    }
+
+    public String getTable() {
+      return table;
+    }
+
+    public void setTable(String table) {
+      this.table = table;
+    }
+
+    public String getColumn() {
+      return column;
+    }
+
+    public void setColumn(String column) {
+      this.column = column;
+    }
+
+    public int getCursorPosition() {
+      return cursorPosition;
+    }
+
+    public void setCursorPosition(int cursorPosition) {
+      this.cursorPosition = cursorPosition;
+    }
+
+    public boolean needLoadSchemas() {
+      if (table == null && column == null) {
+        return true;
+      }
+      return false;
+    }
+
+    public boolean needLoadTables() {
+      if (schema != null && table != null && column == null) {
+        return true;
+      }
+      return false;
+    }
+
+    public boolean needLoadColumns() {
+      if (schema != null && table != null && column != null) {
+        return true;
+      }
+      return false;
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/524d5f2a/jdbc/src/main/resources/interpreter-setting.json
----------------------------------------------------------------------
diff --git a/jdbc/src/main/resources/interpreter-setting.json b/jdbc/src/main/resources/interpreter-setting.json
index fb8b8b2..2bc9b89 100644
--- a/jdbc/src/main/resources/interpreter-setting.json
+++ b/jdbc/src/main/resources/interpreter-setting.json
@@ -22,6 +22,12 @@
         "defaultValue": "",
         "description": "The JDBC user password"
       },
+      "default.completer.ttlInSeconds": {
+        "envName": null,
+        "propertyName": "default.completer.ttlInSeconds",
+        "defaultValue": "120",
+        "description": "Time to live sql completer in seconds (-1 to update everytime, 0 to disable update)"
+      },
       "default.driver": {
         "envName": null,
         "propertyName": "default.driver",

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/524d5f2a/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java
----------------------------------------------------------------------
diff --git a/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java b/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java
index 7c2eef3..e310837 100644
--- a/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java
+++ b/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java
@@ -87,7 +87,7 @@ public class JDBCInterpreterTest extends BasicJDBCTestCaseAdapter {
     PreparedStatement insertStatement = connection.prepareStatement("insert into test_table(id, name) values ('a', 'a_name'),('b', 'b_name'),('c', ?);");
     insertStatement.setString(1, null);
     insertStatement.execute();
-    interpreterContext = new InterpreterContext("", "1", null, "", "", new AuthenticationInfo(), null, null, null, null,
+    interpreterContext = new InterpreterContext("", "1", null, "", "", new AuthenticationInfo("testUser"), null, null, null, null,
         null, null);
   }
 
@@ -296,9 +296,9 @@ public class JDBCInterpreterTest extends BasicJDBCTestCaseAdapter {
 
     jdbcInterpreter.interpret("", interpreterContext);
 
-    List<InterpreterCompletion> completionList = jdbcInterpreter.completion("sel", 3, null);
+    List<InterpreterCompletion> completionList = jdbcInterpreter.completion("sel", 3, interpreterContext);
 
-    InterpreterCompletion correctCompletionKeyword = new InterpreterCompletion("select ", "select ", CompletionType.keyword.name());
+    InterpreterCompletion correctCompletionKeyword = new InterpreterCompletion("select", "select", CompletionType.keyword.name());
 
     assertEquals(1, completionList.size());
     assertEquals(true, completionList.contains(correctCompletionKeyword));

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/524d5f2a/jdbc/src/test/java/org/apache/zeppelin/jdbc/SqlCompleterTest.java
----------------------------------------------------------------------
diff --git a/jdbc/src/test/java/org/apache/zeppelin/jdbc/SqlCompleterTest.java b/jdbc/src/test/java/org/apache/zeppelin/jdbc/SqlCompleterTest.java
index 999f7de..19150cc 100644
--- a/jdbc/src/test/java/org/apache/zeppelin/jdbc/SqlCompleterTest.java
+++ b/jdbc/src/test/java/org/apache/zeppelin/jdbc/SqlCompleterTest.java
@@ -86,7 +86,7 @@ public class SqlCompleterTest {
     private void expectedCompletions(String buffer, int cursor,
         Set<InterpreterCompletion> expected) {
       if (StringUtils.isNotEmpty(buffer) && buffer.length() > cursor) {
-        buffer = buffer.substring(0, cursor + 1);
+        buffer = buffer.substring(0, cursor);
       }
 
       List<InterpreterCompletion> candidates = new ArrayList<>();
@@ -140,13 +140,10 @@ public class SqlCompleterTest {
   private ArgumentCompleter.WhitespaceArgumentDelimiter delimiter =
       new ArgumentCompleter.WhitespaceArgumentDelimiter();
 
-  private SqlCompleter sqlCompleter = new SqlCompleter();
+  private SqlCompleter sqlCompleter = new SqlCompleter(0);
 
   @Before
   public void beforeTest() throws IOException, SQLException {
-
-    Map<String, Set<String>> tables = new HashMap<>();
-    Map<String, Set<String>> columns = new HashMap<>();
     Set<String> schemas = new HashSet<>();
     Set<String> keywords = new HashSet<>();
 
@@ -158,37 +155,42 @@ public class SqlCompleterTest {
     keywords.add("LIMIT");
     keywords.add("FROM");
 
+    sqlCompleter.initKeywords(keywords);
+
     schemas.add("prod_dds");
     schemas.add("prod_emart");
 
+    sqlCompleter.initSchemas(schemas);
+
     Set<String> prod_dds_tables = new HashSet<>();
     prod_dds_tables.add("financial_account");
     prod_dds_tables.add("customer");
 
+    sqlCompleter.initTables("prod_dds", prod_dds_tables);
+
     Set<String> prod_emart_tables = new HashSet<>();
     prod_emart_tables.add("financial_account");
 
-    tables.put("prod_dds", prod_dds_tables);
-    tables.put("prod_emart", prod_emart_tables);
+    sqlCompleter.initTables("prod_emart", prod_emart_tables);
 
     Set<String> prod_dds_financial_account_columns = new HashSet<>();
     prod_dds_financial_account_columns.add("account_rk");
     prod_dds_financial_account_columns.add("account_id");
 
+    sqlCompleter.initColumns("prod_dds.financial_account", prod_dds_financial_account_columns);
+
     Set<String> prod_dds_customer_columns = new HashSet<>();
     prod_dds_customer_columns.add("customer_rk");
     prod_dds_customer_columns.add("name");
     prod_dds_customer_columns.add("birth_dt");
 
+    sqlCompleter.initColumns("prod_dds.customer", prod_dds_customer_columns);
+
     Set<String> prod_emart_financial_account_columns = new HashSet<>();
     prod_emart_financial_account_columns.add("account_rk");
     prod_emart_financial_account_columns.add("balance_amt");
 
-    columns.put("prod_dds.financial_account", prod_dds_financial_account_columns);
-    columns.put("prod_dds.customer", prod_dds_customer_columns);
-    columns.put("prod_emart.financial_account", prod_emart_financial_account_columns);
-
-    sqlCompleter.init(schemas, tables, columns, keywords);
+    sqlCompleter.initColumns("prod_emart.financial_account", prod_emart_financial_account_columns);
 
     tester = new CompleterTester(sqlCompleter);
   }
@@ -223,8 +225,8 @@ public class SqlCompleterTest {
     int cursor = 0;
     List<InterpreterCompletion> candidates = new ArrayList<>();
     Map<String, String> aliases = new HashMap<>();
-    sqlCompleter.completeName(buffer, cursor, candidates, aliases, true);
-    assertEquals(17, candidates.size());
+    sqlCompleter.completeName(buffer, cursor, candidates, aliases);
+    assertEquals(9, candidates.size());
     assertTrue(candidates.contains(new InterpreterCompletion("prod_dds", "prod_dds", CompletionType.schema.name())));
     assertTrue(candidates.contains(new InterpreterCompletion("prod_emart", "prod_emart", CompletionType.schema.name())));
     assertTrue(candidates.contains(new InterpreterCompletion("SUM", "SUM", CompletionType.keyword.name())));
@@ -234,14 +236,6 @@ public class SqlCompleterTest {
     assertTrue(candidates.contains(new InterpreterCompletion("ORDER", "ORDER", CompletionType.keyword.name())));
     assertTrue(candidates.contains(new InterpreterCompletion("LIMIT", "LIMIT", CompletionType.keyword.name())));
     assertTrue(candidates.contains(new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name())));
-    assertTrue(candidates.contains(new InterpreterCompletion("financial_account", "financial_account", CompletionType.table.name())));
-    assertTrue(candidates.contains(new InterpreterCompletion("customer", "customer", CompletionType.table.name())));
-    assertTrue(candidates.contains(new InterpreterCompletion("account_id", "account_id", CompletionType.column.name())));
-    assertTrue(candidates.contains(new InterpreterCompletion("customer_rk", "customer_rk", CompletionType.column.name())));
-    assertTrue(candidates.contains(new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name())));
-    assertTrue(candidates.contains(new InterpreterCompletion("name", "name", CompletionType.column.name())));
-    assertTrue(candidates.contains(new InterpreterCompletion("birth_dt", "birth_dt", CompletionType.column.name())));
-    assertTrue(candidates.contains(new InterpreterCompletion("balance_amt", "balance_amt", CompletionType.column.name())));
   }
 
   @Test
@@ -250,7 +244,7 @@ public class SqlCompleterTest {
     int cursor = 3;
     List<InterpreterCompletion> candidates = new ArrayList<>();
     Map<String, String> aliases = new HashMap<>();
-    sqlCompleter.completeName(buffer, cursor, candidates, aliases, false);
+    sqlCompleter.completeName(buffer, cursor, candidates, aliases);
     assertEquals(2, candidates.size());
     assertTrue(candidates.contains(new InterpreterCompletion("prod_dds", "prod_dds", CompletionType.schema.name())));
     assertTrue(candidates.contains(new InterpreterCompletion("prod_emart", "prod_emart", CompletionType.schema.name())));
@@ -262,7 +256,7 @@ public class SqlCompleterTest {
     int cursor = 11;
     List<InterpreterCompletion> candidates = new ArrayList<>();
     Map<String, String> aliases = new HashMap<>();
-    sqlCompleter.completeName(buffer, cursor, candidates, aliases, false);
+    sqlCompleter.completeName(buffer, cursor, candidates, aliases);
     assertEquals(1, candidates.size());
     assertTrue(candidates.contains(new InterpreterCompletion("financial_account", "financial_account", CompletionType.table.name())));
   }
@@ -273,7 +267,7 @@ public class SqlCompleterTest {
     int cursor = 30;
     List<InterpreterCompletion> candidates = new ArrayList<>();
     Map<String, String> aliases = new HashMap<>();
-    sqlCompleter.completeName(buffer, cursor, candidates, aliases, true);
+    sqlCompleter.completeName(buffer, cursor, candidates, aliases);
     assertEquals(2, candidates.size());
     assertTrue(candidates.contains(new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name())));
     assertTrue(candidates.contains(new InterpreterCompletion("account_id", "account_id", CompletionType.column.name())));
@@ -286,7 +280,7 @@ public class SqlCompleterTest {
     List<InterpreterCompletion> candidates = new ArrayList<>();
     Map<String, String> aliases = new HashMap<>();
     aliases.put("a", "prod_dds.financial_account");
-    sqlCompleter.completeName(buffer, cursor, candidates, aliases, true);
+    sqlCompleter.completeName(buffer, cursor, candidates, aliases);
     assertEquals(2, candidates.size());
     assertTrue(candidates.contains(new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name())));
     assertTrue(candidates.contains(new InterpreterCompletion("account_id", "account_id", CompletionType.column.name())));
@@ -299,7 +293,7 @@ public class SqlCompleterTest {
     List<InterpreterCompletion> candidates = new ArrayList<>();
     Map<String, String> aliases = new HashMap<>();
     aliases.put("a", "prod_dds.financial_account");
-    sqlCompleter.completeName(buffer, cursor, candidates, aliases, true);
+    sqlCompleter.completeName(buffer, cursor, candidates, aliases);
     assertEquals(2, candidates.size());
     assertTrue(candidates.contains(new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name())));
     assertTrue(candidates.contains(new InterpreterCompletion("account_id", "account_id", CompletionType.column.name())));
@@ -308,14 +302,14 @@ public class SqlCompleterTest {
   @Test
   public void testSchemaAndTable() {
     String buffer = "select * from prod_emart.fi";
-    tester.buffer(buffer).from(19).to(23).expect(newHashSet(new InterpreterCompletion("prod_emart ", "prod_emart ", CompletionType.schema.name()))).test();
-    tester.buffer(buffer).from(25).to(27).expect(newHashSet(new InterpreterCompletion("financial_account ", "financial_account ", CompletionType.table.name()))).test();
+    tester.buffer(buffer).from(20).to(23).expect(newHashSet(new InterpreterCompletion("prod_emart", "prod_emart", CompletionType.schema.name()))).test();
+    tester.buffer(buffer).from(25).to(27).expect(newHashSet(new InterpreterCompletion("financial_account", "financial_account", CompletionType.table.name()))).test();
   }
 
   @Test
   public void testEdges() {
     String buffer = "  ORDER  ";
-    tester.buffer(buffer).from(2).to(6).expect(newHashSet(new InterpreterCompletion("ORDER ", "ORDER ", CompletionType.keyword.name()))).test();
+    tester.buffer(buffer).from(3).to(7).expect(newHashSet(new InterpreterCompletion("ORDER", "ORDER", CompletionType.keyword.name()))).test();
     tester.buffer(buffer).from(0).to(1).expect(newHashSet(
         new InterpreterCompletion("ORDER", "ORDER", CompletionType.keyword.name()),
         new InterpreterCompletion("SUBCLASS_ORIGIN", "SUBCLASS_ORIGIN", CompletionType.keyword.name()),
@@ -325,37 +319,29 @@ public class SqlCompleterTest {
         new InterpreterCompletion("SUM", "SUM", CompletionType.keyword.name()),
         new InterpreterCompletion("prod_dds", "prod_dds", CompletionType.schema.name()),
         new InterpreterCompletion("SELECT", "SELECT", CompletionType.keyword.name()),
-        new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name()),
-        new InterpreterCompletion("financial_account", "financial_account", CompletionType.table.name()),
-        new InterpreterCompletion("customer", "customer", CompletionType.table.name()),
-        new InterpreterCompletion("account_rk", "account_rk", CompletionType.column.name()),
-        new InterpreterCompletion("account_id", "account_id", CompletionType.column.name()),
-        new InterpreterCompletion("customer_rk", "customer_rk", CompletionType.column.name()),
-        new InterpreterCompletion("name", "name", CompletionType.column.name()),
-        new InterpreterCompletion("birth_dt", "birth_dt", CompletionType.column.name()),
-        new InterpreterCompletion("balance_amt", "balance_amt", CompletionType.column.name())
+        new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name())
     )).test();
   }
 
   @Test
   public void testMultipleWords() {
     String buffer = "SELE FRO LIM";
-    tester.buffer(buffer).from(1).to(3).expect(newHashSet(new InterpreterCompletion("SELECT ", "SELECT ", CompletionType.keyword.name()))).test();
-    tester.buffer(buffer).from(6).to(7).expect(newHashSet(new InterpreterCompletion("FROM ", "FROM ", CompletionType.keyword.name()))).test();
-    tester.buffer(buffer).from(9).to(12).expect(newHashSet(new InterpreterCompletion("LIMIT ", "LIMIT ", CompletionType.keyword.name()))).test();
+    tester.buffer(buffer).from(2).to(4).expect(newHashSet(new InterpreterCompletion("SELECT", "SELECT", CompletionType.keyword.name()))).test();
+    tester.buffer(buffer).from(6).to(8).expect(newHashSet(new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name()))).test();
+    tester.buffer(buffer).from(10).to(12).expect(newHashSet(new InterpreterCompletion("LIMIT", "LIMIT", CompletionType.keyword.name()))).test();
   }
 
   @Test
   public void testMultiLineBuffer() {
     String buffer = " \n SELE\nFRO";
-    tester.buffer(buffer).from(4).to(6).expect(newHashSet(new InterpreterCompletion("SELECT ", "SELECT ", CompletionType.keyword.name()))).test();
-    tester.buffer(buffer).from(9).to(11).expect(newHashSet(new InterpreterCompletion("FROM ", "FROM ", CompletionType.keyword.name()))).test();
+    tester.buffer(buffer).from(5).to(7).expect(newHashSet(new InterpreterCompletion("SELECT", "SELECT", CompletionType.keyword.name()))).test();
+    tester.buffer(buffer).from(9).to(11).expect(newHashSet(new InterpreterCompletion("FROM", "FROM", CompletionType.keyword.name()))).test();
   }
 
   @Test
   public void testMultipleCompletionSuggestions() {
     String buffer = "SU";
-    tester.buffer(buffer).from(1).to(2).expect(newHashSet(
+    tester.buffer(buffer).from(2).to(2).expect(newHashSet(
         new InterpreterCompletion("SUBCLASS_ORIGIN", "SUBCLASS_ORIGIN", CompletionType.keyword.name()),
         new InterpreterCompletion("SUM", "SUM", CompletionType.keyword.name()),
         new InterpreterCompletion("SUBSTRING", "SUBSTRING", CompletionType.keyword.name()))

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/524d5f2a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CachedCompleter.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CachedCompleter.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CachedCompleter.java
new file mode 100644
index 0000000..ef2223e
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CachedCompleter.java
@@ -0,0 +1,44 @@
+/**
+ * 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.completer;
+
+import jline.console.completer.Completer;
+
+/**
+ * Completer with time to live
+ */
+public class CachedCompleter {
+  private Completer completer;
+  private int ttlInSeconds;
+  private long createdAt;
+
+  public CachedCompleter(Completer completer, int ttlInSeconds) {
+    this.completer = completer;
+    this.ttlInSeconds = ttlInSeconds;
+    this.createdAt = System.currentTimeMillis();
+  }
+
+  public boolean isExpired() {
+    if (ttlInSeconds == -1 || (ttlInSeconds > 0 &&
+        (System.currentTimeMillis() - createdAt) / 1000 > ttlInSeconds)) {
+      return true;
+    }
+    return false;
+  }
+
+  public Completer getCompleter() {
+    return completer;
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/524d5f2a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/StringsCompleter.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/StringsCompleter.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/StringsCompleter.java
index c7dcebe..c117441 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/StringsCompleter.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/StringsCompleter.java
@@ -61,8 +61,9 @@ public class StringsCompleter implements Completer {
     if (buffer == null) {
       candidates.addAll(strings);
     } else {
-      String bufferTmp = buffer.toUpperCase();
-      for (String match : strings.tailSet(buffer)) {
+      String part = buffer.substring(0, cursor);
+      String bufferTmp = part.toUpperCase();
+      for (String match : strings.tailSet(part)) {
         String matchTmp = match.toUpperCase();
         if (!matchTmp.startsWith(bufferTmp)) {
           break;


Mime
View raw message