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 <[email protected]>

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 <[email protected]>
Authored: Sat May 20 22:38:11 2017 +0500
Committer: Jongyoul Lee <[email protected]>
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;

Reply via email to