diff --git a/docs/interpreter/jdbc.md b/docs/interpreter/jdbc.md index b7ac45ae44..aad84ea0c9 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. С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) + + default.completer.ttlInSeconds + 120 + Time to live sql completer in seconds + If you want to connect other databases such as `Mysql`, `Redshift` and `Hive`, you need to edit the property values. 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 3272bc1370..65d53a4bb7 100644 --- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java @@ -101,6 +101,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 +130,7 @@ public class JDBCInterpreter extends Interpreter { private final HashMap basePropretiesMap; private final HashMap jdbcUserConfigurationsMap; + private final HashMap sqlCompletersMap; private int maxLineResults; @@ -135,6 +138,7 @@ public class JDBCInterpreter extends Interpreter { super(property); jdbcUserConfigurationsMap = new HashMap<>(); basePropretiesMap = new HashMap<>(); + sqlCompletersMap = new HashMap<>(); maxLineResults = MAX_LINE_DEFAULT; } @@ -188,12 +192,20 @@ public class JDBCInterpreter extends Interpreter { } } - private SqlCompleter createSqlCompleter(Connection jdbcConnection, String propertyKey) { + private SqlCompleter createOrUpdateSqlCompleter(SqlCompleter sqlCompleter, Connection connection, + String propertyKey, String buf, 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); - return completer; + String sqlCompleterTtlKey = String.format("%s.%s", propertyKey, COMPLETER_TTL_KEY); + String schemaFiltersString = getProperty(schemaFiltersKey); + int ttlInSeconds = Integer.valueOf( + StringUtils.defaultIfEmpty(getProperty(sqlCompleterTtlKey), DEFAULT_COMPLETER_TTL) + ); + if (sqlCompleter == null) { + sqlCompleter = new SqlCompleter(ttlInSeconds); + } + + sqlCompleter.createOrUpdateFromConnection(connection, schemaFiltersString, buf, cursor); + return sqlCompleter; } private void initStatementMap() { @@ -787,6 +799,10 @@ public class JDBCInterpreter extends Interpreter { InterpreterContext interpreterContext) { List 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 +812,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; } 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 704ec597c4..b83b5129be 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 tablesCompleters = new HashMap<>(); + private Map tablesCompleters = new HashMap<>(); /** * Contains different completer with column list for every table name * Table names store as schema_name.table_name */ - private Map columnsCompleters = new HashMap<>(); + private Map 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 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 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 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; - * null 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> tables, - Map> 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()); - } - columns.get(schemaTable).add(column); - if (!tables.containsKey(schema)) { - tables.put(schema, new HashSet()); - } - tables.get(schema).add(table); - } - } - } finally { - cols.close(); + private static void fillColumnNames(String schema, String table, DatabaseMetaData meta, + Set 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 getSqlKeywordsCompletions(Connection connection) throws IOException, + public static Set getSqlKeywordsCompletions(DatabaseMetaData meta) throws IOException, SQLException { // Add the default SQL completions @@ -246,12 +220,11 @@ public class SqlCompleter { Set 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); } @@ -307,95 +280,80 @@ public class SqlCompleter { return completions; } - /** - * Initializes local schema completers from list of schema names - * - * @param schemas set of schema names - */ - private void initSchemas(Set 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> tables) { - tablesCompleters.clear(); - for (Map.Entry> 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> columns) { - columnsCompleters.clear(); - for (Map.Entry> 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 schemas, Map> tables, - Map> columns, Set 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) { + public void createOrUpdateFromConnection(Connection connection, String schemaFiltersString, + String buffer, int cursor) { if (schemaFiltersString == null) { schemaFiltersString = StringUtils.EMPTY; } List schemaFilters = Arrays.asList(schemaFiltersString.split(",")); + CursorArgument cursorArgument = parseCursorArgument(buffer, cursor); try (Connection c = connection) { - Map> tables = new HashMap<>(); - Map> columns = new HashMap<>(); + Set tables = new HashSet<>(); + Set columns = new HashSet<>(); Set schemas = new HashSet<>(); Set catalogs = new HashSet<>(); - Set keywords = getSqlKeywordsCompletions(connection); - if (connection != null) { - schemas = getSchemaNames(connection.getMetaData(), schemaFilters); - catalogs = getCatalogNames(connection.getMetaData(), schemaFilters); - if (schemas.size() == 0) { - schemas.addAll(catalogs); + Set keywords = new HashSet<>(); + + if (c != null) { + DatabaseMetaData databaseMetaData = c.getMetaData(); + if (keywordCompleter == null || keywordCompleter.getCompleter() == null + || keywordCompleter.isExpired()) { + keywords = getSqlKeywordsCompletions(databaseMetaData); + keywordCompleter = new CachedCompleter(new StringsCompleter(keywords), 0); } - 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); + } + if (!schemas.isEmpty()) { + schemasCompleter = new CachedCompleter( + new StringsCompleter(new TreeSet<>(schemas)), ttlInSeconds); } } + + CachedCompleter tablesCompleter = tablesCompleters.get(cursorArgument.getSchema()); + if (cursorArgument.needLoadTables() && + (tablesCompleter == null || tablesCompleter.isExpired())) { + fillTableNames(cursorArgument.getSchema(), databaseMetaData, tables); + if (!tables.isEmpty()) { + tablesCompleters.put(cursorArgument.getSchema(), new CachedCompleter( + new StringsCompleter(new TreeSet<>(tables)), ttlInSeconds)); + } + } + + 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); + if (!columns.isEmpty()) { + columnsCompleters.put(schemaTable, + new CachedCompleter(new StringsCompleter(columns), ttlInSeconds)); + } + } + + 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); } } @@ -422,7 +380,7 @@ public class SqlCompleter { * @return -1 in case of no candidates found, 0 otherwise */ private int completeKeyword(String buffer, int cursor, List candidates) { - return keywordCompleter.complete(buffer, cursor, candidates); + return keywordCompleter.getCompleter().complete(buffer, cursor, candidates); } /** @@ -431,7 +389,7 @@ public class SqlCompleter { * @return -1 in case of no candidates found, 0 otherwise */ private int completeSchema(String buffer, int cursor, List candidates) { - return schemasCompleter.complete(buffer, cursor, candidates); + return schemasCompleter.getCompleter().complete(buffer, cursor, candidates); } /** @@ -441,21 +399,12 @@ public class SqlCompleter { */ private int completeTable(String schema, String buffer, int cursor, List candidates) { - if (schema == null) { - int res = -1; - Set 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 +414,12 @@ public class SqlCompleter { */ private int completeColumn(String schema, String table, String buffer, int cursor, List candidates) { - if (table == null && schema == null) { - int res = -1; - Set 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 +428,56 @@ 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 candidates, - Map aliases, boolean isColumnAllowed) { + Map aliases) { - // points divide the name to the schema, table and column - find them - int pointPos1 = -1; - int pointPos2 = -1; + CursorArgument cursorArgument = parseCursorArgument(buffer, cursor); - 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 keywordsCandidates = new ArrayList(); List schemaCandidates = new ArrayList<>(); - List tableCandidates = new ArrayList<>(); - List 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 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 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 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 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 +493,90 @@ public class SqlCompleter { candidate.toString(), meta)); } } + + private CursorArgument parseCursorArgument(String buffer, int cursor) { + CursorArgument result = new CursorArgument(); + String buf = buffer.substring(0, cursor); + if (StringUtils.isNotEmpty(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; + } + } } diff --git a/jdbc/src/main/resources/interpreter-setting.json b/jdbc/src/main/resources/interpreter-setting.json index fb8b8b2a23..b1d8c232b5 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" + }, "default.driver": { "envName": null, "propertyName": "default.driver", 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 0000000000..ef2223eb2c --- /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; + } +} 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 c7dcebe79f..c117441058 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;