/*
* Copyright 2014 TWO SIGMA OPEN SOURCE, LLC
*
* Licensed 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 com.twosigma.beaker.sql.autocomplete;
import com.twosigma.beaker.autocomplete.ClasspathScanner;
import com.twosigma.beaker.sql.autocomplete.db.DbCache;
import com.twosigma.beaker.sql.autocomplete.db.DbExplorerFactory;
import com.twosigma.beaker.sql.autocomplete.db.DbInfo;
import com.twosigma.beaker.sql.ConnectionStringHolder;
import com.twosigma.beaker.sql.JDBCClient;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class SQLAutocomplete {
private static final String[] SQL_KEYS = {
"ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND",
"AS", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY", "CASCADE", "CASE", "CAST",
"CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE",
"CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC",
"DETACH", "DISTINCT", "DROP", "EACH", "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN",
"FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF", "IGNORE", "IMMEDIATE", "IN",
"INDEX", "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN",
"KEY", "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL", "NULL", "OF", "OFFSET", "ON",
"OR", "ORDER", "OUTER", "PLAN", "PRAGMA", "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP",
"REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT", "ROLLBACK", "ROW", "SAVEPOINT", "SELECT",
"SET", "TABLE", "TEMP", "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE", "UPDATE",
"USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WITH", "WITHOUT" };
private static final String PARAM_CHAR = "%";
private static final String[] PARAM_KEYS = { PARAM_CHAR + PARAM_CHAR +"beakerDB", PARAM_CHAR + PARAM_CHAR + "inputs"};
private final JDBCClient jdbcClient;
private final String sessionId;
private ConnectionStringHolder defaultConnectionString;
private final Map<String, ConnectionStringHolder> namedConnectionString;
private final DbCache cache;
public SQLAutocomplete(ClasspathScanner _cps, JDBCClient jdbcClient, String sessionId, ConnectionStringHolder defaultConnectionString, Map<String, ConnectionStringHolder> namedConnectionString) {
super();
this.jdbcClient = jdbcClient;
this.sessionId = sessionId;
this.defaultConnectionString = defaultConnectionString;
this.namedConnectionString = namedConnectionString;
this.cache = DbExplorerFactory.getDbCache();
}
private List<String> findKeys(final String key, final String[] keys){
final List<String> ret = new ArrayList<String>();
if (key == null || key.trim().length() == 0) {
ret.addAll(Arrays.asList(keys));
} else {
final String lowerTxt = key.toLowerCase(Locale.ROOT);
for (String str : Arrays.asList(keys)) {
if (str.toLowerCase(Locale.ROOT).startsWith(lowerTxt)) {
ret.add(str);
}
}
}
return ret;
}
private List<String> findSqlKeys(final String key){
return findKeys(key, SQL_KEYS);
}
private List<String> findParamKeys(final String key){
return findKeys(key, PARAM_KEYS);
}
private String findKey(final String txt, final int cur){
if (cur <= 0 || Character.isWhitespace(txt.charAt(cur - 1))) {
return "";
} else {
String res = "";
int eos = cur - 1;
for (int i = eos; i >= 0; i--) {
final boolean isIdentifier = Character.isUnicodeIdentifierPart(txt.charAt(i)) || PARAM_CHAR.charAt(0) == txt.charAt(i);
if (isIdentifier) {
eos = i;
}
if (!isIdentifier || i == 0) {
res = new String(txt.substring(eos, cur));
break;
}
}
return res;
}
}
public List<String> doAutocomplete(final String txt, final int cur) {
List<String> ret = new LinkedList<String>();
if (cur == 0) {
return findParamKeys(null);
}
final String key = findKey(txt, cur);
if (key != null && key.length() > 0 && key.startsWith(PARAM_CHAR)) {
return findParamKeys(key);
} else {
ret.addAll(findSqlKeys(key));
}
final DbInfo dbInfo = DbExplorerFactory.getDbInfo(txt, jdbcClient, sessionId, defaultConnectionString, namedConnectionString);
if (dbInfo != null) {
List<String> dbRet = null;
if (cur > key.length() + 1 && (txt.charAt(cur - key.length() - 1) == '.' || ".".equals(key))) {
String fieldKey = key;
int searchTableIndex = cur - key.length() - 1;
if (".".equals(key)) {
fieldKey = "";
searchTableIndex = cur - 1;
}
final String tableName = findKey(txt, searchTableIndex);
if (tableName != null) {
return dbInfo.getTableFieldNames(cache, null, tableName, fieldKey);
}
} else {
dbRet = dbInfo.getTableNames(cache, null, key);
}
if (dbRet != null) {
ret.addAll(0, dbRet);
}
}
return ret;
}
}