mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
ZEPPELIN-215: PostgreSQL completer - initial implementation
This commit is contained in:
parent
6ac8b3d4ff
commit
c6acd0ea28
8 changed files with 441 additions and 7 deletions
1
pom.xml
1
pom.xml
|
|
@ -402,6 +402,7 @@
|
|||
<version>0.11</version>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>**/*.keywords</exclude>
|
||||
<exclude>**/.idea/</exclude>
|
||||
<exclude>**/*.iml</exclude>
|
||||
<exclude>.git/</exclude>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,17 @@
|
|||
<version>${postgresql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jline</groupId>
|
||||
<artifactId>jline</artifactId>
|
||||
<version>2.12.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@
|
|||
*/
|
||||
package org.apache.zeppelin.postgresql;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
|
|
@ -34,6 +36,9 @@ import org.apache.zeppelin.scheduler.SchedulerFactory;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* PostgreSQL interpreter for Zeppelin. This interpreter can also be used for accessing HAWQ and
|
||||
* GreenplumDB.
|
||||
|
|
@ -55,6 +60,8 @@ import org.slf4j.LoggerFactory;
|
|||
* GROUP BY store_id;
|
||||
* }
|
||||
* </p>
|
||||
*
|
||||
* For SQL auto-completion use the (Ctrl+.) shortcut.
|
||||
*/
|
||||
public class PostgreSqlInterpreter extends Interpreter {
|
||||
|
||||
|
|
@ -99,6 +106,17 @@ public class PostgreSqlInterpreter extends Interpreter {
|
|||
private Exception exceptionOnConnect;
|
||||
private int maxResult;
|
||||
|
||||
private SqlCompleter sqlCompleter;
|
||||
|
||||
private static final Function<CharSequence, String> sequenceToStringTransformer =
|
||||
new Function<CharSequence, String>() {
|
||||
public String apply(CharSequence seq) {
|
||||
return seq.toString();
|
||||
}
|
||||
};
|
||||
|
||||
private static final List<String> NO_COMPLETION = new ArrayList<String>();
|
||||
|
||||
public PostgreSqlInterpreter(Properties property) {
|
||||
super(property);
|
||||
}
|
||||
|
|
@ -123,10 +141,12 @@ public class PostgreSqlInterpreter extends Interpreter {
|
|||
|
||||
jdbcConnection = DriverManager.getConnection(url, user, password);
|
||||
|
||||
sqlCompleter = new SqlCompleter(SqlCompleter.getSqlCompleterTokens(jdbcConnection, false));
|
||||
|
||||
exceptionOnConnect = null;
|
||||
logger.info("Successfully created psql connection");
|
||||
|
||||
} catch (ClassNotFoundException | SQLException e) {
|
||||
} catch (ClassNotFoundException | SQLException | IOException e) {
|
||||
logger.error("Cannot open connection", e);
|
||||
exceptionOnConnect = e;
|
||||
}
|
||||
|
|
@ -264,7 +284,13 @@ public class PostgreSqlInterpreter extends Interpreter {
|
|||
|
||||
@Override
|
||||
public List<String> completion(String buf, int cursor) {
|
||||
return null;
|
||||
|
||||
List<CharSequence> candidates = new ArrayList<CharSequence>();
|
||||
if (sqlCompleter.complete(buf, cursor, candidates) >= 0) {
|
||||
return Lists.transform(candidates, sequenceToStringTransformer);
|
||||
} else {
|
||||
return NO_COMPLETION;
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxResult() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,200 @@
|
|||
/**
|
||||
* 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.postgresql;
|
||||
|
||||
/*
|
||||
* This source file is based on code taken from SQLLine 1.0.2 See SQLLine notice in LICENSE
|
||||
*/
|
||||
import static java.lang.Character.isWhitespace;
|
||||
import static org.apache.commons.lang.StringUtils.isBlank;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import jline.console.completer.ArgumentCompleter.ArgumentList;
|
||||
import jline.console.completer.ArgumentCompleter.WhitespaceArgumentDelimiter;
|
||||
import jline.console.completer.StringsCompleter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* SQL auto complete functionality for the PostgreSqlInterpreter.
|
||||
*/
|
||||
public class SqlCompleter extends StringsCompleter {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(SqlCompleter.class);
|
||||
|
||||
/**
|
||||
* Delimiter that can split SQL statement in keyword list
|
||||
*/
|
||||
private WhitespaceArgumentDelimiter sqlDelimiter = new WhitespaceArgumentDelimiter() {
|
||||
@Override
|
||||
public boolean isDelimiterChar(CharSequence buffer, int pos) {
|
||||
return super.isDelimiterChar(buffer, pos) || (buffer.charAt(pos) == '.')
|
||||
|| (buffer.charAt(pos) == ';') || (buffer.charAt(pos) == ':');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public SqlCompleter(Set<String> completions) {
|
||||
super(completions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
|
||||
|
||||
if (isBlank(buffer) || (cursor > buffer.length() + 1)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The delimiter breaks the buffer into separate words (arguments), separated by the
|
||||
// whitespaces.
|
||||
ArgumentList argumentList = sqlDelimiter.delimit(buffer, cursor);
|
||||
String argument = argumentList.getCursorArgument();
|
||||
// cursor in the selected argument
|
||||
int argumentPosition = argumentList.getArgumentPosition();
|
||||
|
||||
if (isBlank(argument)) {
|
||||
int argumentsCount = argumentList.getArguments().length;
|
||||
if (argumentsCount <= 0 || isWhitespace(buffer.charAt(buffer.length() - 1))) {
|
||||
return -1;
|
||||
}
|
||||
argument = argumentList.getArguments()[argumentsCount - 1];
|
||||
argumentPosition = argument.length();
|
||||
}
|
||||
|
||||
int complete = super.complete(argument, argumentPosition, candidates);
|
||||
|
||||
logger.debug("complete:" + complete + ", size:" + candidates.size());
|
||||
|
||||
return complete;
|
||||
}
|
||||
|
||||
public static Set<String> getSqlCompleterTokens(Connection connection, boolean skipmeta)
|
||||
throws IOException, SQLException {
|
||||
|
||||
// Add the default SQL completions
|
||||
String keywords =
|
||||
new BufferedReader(new InputStreamReader(
|
||||
SqlCompleter.class.getResourceAsStream("/ansi.sql.keywords"))).readLine();
|
||||
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
|
||||
// Add the driver specific SQL completions
|
||||
String driverSpecificKeywords =
|
||||
"/" + metaData.getDriverName().replace(" ", "-").toLowerCase() + "-sql.keywords";
|
||||
|
||||
logger.info("JDBC DriverName:" + driverSpecificKeywords);
|
||||
|
||||
if (SqlCompleter.class.getResource(driverSpecificKeywords) != null) {
|
||||
logger.info("JDBC DriverName: 2" + driverSpecificKeywords);
|
||||
String driverKeywords =
|
||||
new BufferedReader(new InputStreamReader(
|
||||
SqlCompleter.class.getResourceAsStream(driverSpecificKeywords))).readLine();
|
||||
keywords += "," + driverKeywords.toUpperCase();
|
||||
}
|
||||
|
||||
Set<String> completions = new TreeSet<String>();
|
||||
|
||||
|
||||
// Add the keywords from the current JDBC connection
|
||||
try {
|
||||
keywords += "," + metaData.getSQLKeywords();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get SQL key words from database metadata: " + e, e);
|
||||
}
|
||||
try {
|
||||
keywords += "," + metaData.getStringFunctions();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get string function names from database metadata: " + e, e);
|
||||
}
|
||||
try {
|
||||
keywords += "," + metaData.getNumericFunctions();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get numeric function names from database metadata: " + e, e);
|
||||
}
|
||||
try {
|
||||
keywords += "," + metaData.getSystemFunctions();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get system function names from database metadata: " + e, e);
|
||||
}
|
||||
try {
|
||||
keywords += "," + metaData.getTimeDateFunctions();
|
||||
} catch (Exception e) {
|
||||
logger.debug("fail to get time date function names from database metadata: " + e, e);
|
||||
}
|
||||
|
||||
// Also allow lower-case versions of all the keywords
|
||||
keywords += "," + keywords.toLowerCase();
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(keywords, ", ");
|
||||
while (tok.hasMoreTokens()) {
|
||||
completions.add(tok.nextToken());
|
||||
}
|
||||
|
||||
// now add the tables and columns from the current connection
|
||||
if (!(skipmeta)) {
|
||||
String[] columns = getColumnNames(connection.getMetaData());
|
||||
for (int i = 0; columns != null && i < columns.length; i++) {
|
||||
completions.add(columns[i++]);
|
||||
}
|
||||
}
|
||||
|
||||
return completions;
|
||||
}
|
||||
|
||||
private static String[] getColumnNames(DatabaseMetaData meta) throws SQLException {
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
try {
|
||||
ResultSet columns = getColumns(meta, "%");
|
||||
try {
|
||||
|
||||
while (columns.next()) {
|
||||
// add the following strings:
|
||||
// 1. column name
|
||||
// 2. table name
|
||||
// 3. tablename.columnname
|
||||
String name = columns.getString("TABLE_NAME");
|
||||
names.add(name);
|
||||
names.add(columns.getString("COLUMN_NAME"));
|
||||
names.add(columns.getString("TABLE_NAME") + "." + columns.getString("COLUMN_NAME"));
|
||||
}
|
||||
} finally {
|
||||
columns.close();
|
||||
}
|
||||
|
||||
return names.toArray(new String[0]);
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to tretrieve the column name", t);
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
static ResultSet getColumns(DatabaseMetaData metaData, String table) throws SQLException {
|
||||
return metaData.getColumns(metaData.getConnection().getCatalog(), null, table, "%");
|
||||
}
|
||||
}
|
||||
1
postgresql/src/main/resources/ansi.sql.keywords
Normal file
1
postgresql/src/main/resources/ansi.sql.keywords
Normal file
|
|
@ -0,0 +1 @@
|
|||
ABSOLUTE,ACTION,ADD,ALL,ALLOCATE,ALTER,AND,ANY,ARE,AS,ASC,ASSERTION,AT,AUTHORIZATION,AVG,BEGIN,BETWEEN,BIT,BIT_LENGTH,BOTH,BY,CASCADE,CASCADED,CASE,CAST,CATALOG,CHAR,CHARACTER,CHAR_LENGTH,CHARACTER_LENGTH,CHECK,CLOSE,CLUSTER,COALESCE,COLLATE,COLLATION,COLUMN,COMMIT,CONNECT,CONNECTION,CONSTRAINT,CONSTRAINTS,CONTINUE,CONVERT,CORRESPONDING,COUNT,CREATE,CROSS,CURRENT,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,CURRENT_USER,CURSOR,DATE,DAY,DEALLOCATE,DEC,DECIMAL,DECLARE,DEFAULT,DEFERRABLE,DEFERRED,DELETE,DESC,DESCRIBE,DESCRIPTOR,DIAGNOSTICS,DISCONNECT,DISTINCT,DOMAIN,DOUBLE,DROP,ELSE,END,END-EXEC,ESCAPE,EXCEPT,EXCEPTION,EXEC,EXECUTE,EXISTS,EXTERNAL,EXTRACT,FALSE,FETCH,FIRST,FLOAT,FOR,FOREIGN,FOUND,FROM,FULL,GET,GLOBAL,GO,GOTO,GRANT,GROUP,HAVING,HOUR,IDENTITY,IMMEDIATE,IN,INDICATOR,INITIALLY,INNER,INPUT,INSENSITIVE,INSERT,INT,INTEGER,INTERSECT,INTERVAL,INTO,IS,ISOLATION,JOIN,KEY,LANGUAGE,LAST,LEADING,LEFT,LEVEL,LIKE,LOCAL,LOWER,MATCH,MAX,MIN,MINUTE,MODULE,MONTH,NAMES,NATIONAL,NATURAL,NCHAR,NEXT,NO,NOT,NULL,NULLIF,NUMERIC,OCTET_LENGTH,OF,ON,ONLY,OPEN,OPTION,OR,ORDER,OUTER,OUTPUT,OVERLAPS,OVERWRITE,PAD,PARTIAL,PARTITION,POSITION,PRECISION,PREPARE,PRESERVE,PRIMARY,PRIOR,PRIVILEGES,PROCEDURE,PUBLIC,READ,REAL,REFERENCES,RELATIVE,RESTRICT,REVOKE,RIGHT,ROLLBACK,ROWS,SCHEMA,SCROLL,SECOND,SECTION,SELECT,SESSION,SESSION_USER,SET,SIZE,SMALLINT,SOME,SPACE,SQL,SQLCODE,SQLERROR,SQLSTATE,SUBSTRING,SUM,SYSTEM_USER,TABLE,TEMPORARY,THEN,TIME,TIMESTAMP,TIMEZONE_HOUR,TIMEZONE_MINUTE,TO,TRAILING,TRANSACTION,TRANSLATE,TRANSLATION,TRIM,TRUE,UNION,UNIQUE,UNKNOWN,UPDATE,UPPER,USAGE,USER,USING,VALUE,VALUES,VARCHAR,VARYING,VIEW,WHEN,WHENEVER,WHERE,WITH,WORK,WRITE,YEAR,ZONE,ADA,C,CATALOG_NAME,CHARACTER_SET_CATALOG,CHARACTER_SET_NAME,CHARACTER_SET_SCHEMA,CLASS_ORIGIN,COBOL,COLLATION_CATALOG,COLLATION_NAME,COLLATION_SCHEMA,COLUMN_NAME,COMMAND_FUNCTION,COMMITTED,CONDITION_NUMBER,CONNECTION_NAME,CONSTRAINT_CATALOG,CONSTRAINT_NAME,CONSTRAINT_SCHEMA,CURSOR_NAME,DATA,DATETIME_INTERVAL_CODE,DATETIME_INTERVAL_PRECISION,DYNAMIC_FUNCTION,FORTRAN,LENGTH,MESSAGE_LENGTH,MESSAGE_OCTET_LENGTH,MESSAGE_TEXT,MORE,MUMPS,NAME,NULLABLE,NUMBER,PASCAL,PLI,REPEATABLE,RETURNED_LENGTH,RETURNED_OCTET_LENGTH,RETURNED_SQLSTATE,ROW_COUNT,SCALE,SCHEMA_NAME,SERIALIZABLE,SERVER_NAME,SUBCLASS_ORIGIN,TABLE_NAME,TYPE,UNCOMMITTED,UNNAMED,LIMIT
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -14,13 +14,22 @@
|
|||
*/
|
||||
package org.apache.zeppelin.postgresql;
|
||||
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.*;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.DEFAULT_JDBC_DRIVER_NAME;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.DEFAULT_JDBC_URL;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.DEFAULT_JDBC_USER_NAME;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.DEFAULT_JDBC_USER_PASSWORD;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.DEFAULT_MAX_RESULT;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.POSTGRESQL_SERVER_DRIVER_NAME;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.POSTGRESQL_SERVER_MAX_RESULT;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.POSTGRESQL_SERVER_PASSWORD;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.POSTGRESQL_SERVER_URL;
|
||||
import static org.apache.zeppelin.postgresql.PostgreSqlInterpreter.POSTGRESQL_SERVER_USER;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
|
@ -28,8 +37,6 @@ import java.util.Properties;
|
|||
import org.apache.zeppelin.interpreter.InterpreterResult;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Matchers;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import com.mockrunner.jdbc.BasicJDBCTestCaseAdapter;
|
||||
import com.mockrunner.jdbc.StatementResultSetHandler;
|
||||
|
|
@ -221,4 +228,12 @@ public class PostgreSqlInterpreterTest extends BasicJDBCTestCaseAdapter {
|
|||
verifyAllResultSetsClosed();
|
||||
verifyAllStatementsClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoCompletion() throws SQLException {
|
||||
psqlInterpreter.open();
|
||||
assertEquals(1, psqlInterpreter.completion("SEL", 0).size());
|
||||
assertEquals("SELECT ", psqlInterpreter.completion("SEL", 0).iterator().next());
|
||||
assertEquals(0, psqlInterpreter.completion("SEL", 100).size());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
/**
|
||||
* 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.postgresql;
|
||||
|
||||
import static com.google.common.collect.Sets.newHashSet;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jline.console.completer.Completer;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.mockrunner.jdbc.BasicJDBCTestCaseAdapter;
|
||||
|
||||
public class SqlCompleterTest extends BasicJDBCTestCaseAdapter {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(SqlCompleterTest.class);
|
||||
|
||||
private final static Set<String> EMPTY = new HashSet<String>();
|
||||
|
||||
private CompleterTester tester;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws IOException, SQLException {
|
||||
SqlCompleter sqlCompleter =
|
||||
new SqlCompleter(SqlCompleter.getSqlCompleterTokens(getJDBCMockObjectFactory()
|
||||
.getMockConnection(), false));
|
||||
tester = new CompleterTester(sqlCompleter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAfterBufferEnd() {
|
||||
String buffer = "ORDER";
|
||||
// Up to 2 white spaces after the buffer end, the completer still uses the last argument
|
||||
tester.buffer(buffer).from(0).to(buffer.length() + 1).expect(newHashSet("ORDER ")).test();
|
||||
// 2 white spaces or more behind the buffer end the completer returns empty result
|
||||
tester.buffer(buffer).from(buffer.length() + 2).to(buffer.length() + 5).expect(EMPTY).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEdges() {
|
||||
String buffer = " ORDER ";
|
||||
tester.buffer(buffer).from(0).to(7).expect(newHashSet("ORDER ")).test();
|
||||
tester.buffer(buffer).from(8).to(15).expect(EMPTY).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleWords() {
|
||||
String buffer = " SELE fro LIM";
|
||||
tester.buffer(buffer).from(0).to(6).expect(newHashSet("SELECT ")).test();
|
||||
tester.buffer(buffer).from(7).to(11).expect(newHashSet("from ")).test();
|
||||
tester.buffer(buffer).from(12).to(19).expect(newHashSet("LIMIT ")).test();
|
||||
tester.buffer(buffer).from(20).to(24).expect(EMPTY).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiLineBuffer() {
|
||||
String buffer = " \n SELE \n fro";
|
||||
tester.buffer(buffer).from(0).to(7).expect(newHashSet("SELECT ")).test();
|
||||
tester.buffer(buffer).from(8).to(14).expect(newHashSet("from ")).test();
|
||||
tester.buffer(buffer).from(15).to(17).expect(EMPTY).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleCompletionSuggestions() {
|
||||
String buffer = " SU";
|
||||
tester.buffer(buffer).from(0).to(5).expect(newHashSet("SUBCLASS_ORIGIN", "SUM", "SUBSTRING"))
|
||||
.test();
|
||||
tester.buffer(buffer).from(6).to(7).expect(EMPTY).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommaDelimiter() {
|
||||
String buffer = " order.select ";
|
||||
tester.buffer(buffer).from(0).to(7).expect(newHashSet("order ")).test();
|
||||
tester.buffer(buffer).from(8).to(14).expect(newHashSet("select ")).test();
|
||||
tester.buffer(buffer).from(15).to(17).expect(EMPTY).test();
|
||||
}
|
||||
|
||||
public class CompleterTester {
|
||||
|
||||
private Completer completer;
|
||||
|
||||
private String buffer;
|
||||
private int fromCursor;
|
||||
private int toCursor;
|
||||
private Set<String> expectedCompletions;
|
||||
|
||||
public CompleterTester(Completer completer) {
|
||||
this.completer = completer;
|
||||
}
|
||||
|
||||
public CompleterTester buffer(String buffer) {
|
||||
this.buffer = buffer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompleterTester from(int fromCursor) {
|
||||
this.fromCursor = fromCursor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompleterTester to(int toCursor) {
|
||||
this.toCursor = toCursor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompleterTester expect(Set<String> expectedCompletions) {
|
||||
this.expectedCompletions = expectedCompletions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
for (int c = fromCursor; c <= toCursor; c++) {
|
||||
expectedCompletions(buffer, c, expectedCompletions);
|
||||
}
|
||||
}
|
||||
|
||||
private void expectedCompletions(String buffer, int cursor, Set<String> expected) {
|
||||
|
||||
ArrayList<CharSequence> candidates = new ArrayList<CharSequence>();
|
||||
|
||||
completer.complete(buffer, cursor, candidates);
|
||||
|
||||
String explain = explain(buffer, cursor, candidates);
|
||||
|
||||
logger.info(explain);
|
||||
|
||||
assertEquals("Buffer [" + buffer.replace(" ", ".") + "] and Cursor[" + cursor + "] "
|
||||
+ explain, expected, newHashSet(candidates));
|
||||
}
|
||||
|
||||
private String explain(String buffer, int cursor, ArrayList<CharSequence> candidates) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
for (int i = 0; i <= Math.max(cursor, buffer.length()); i++) {
|
||||
if (i == cursor) {
|
||||
sb.append("(");
|
||||
}
|
||||
if (i >= buffer.length()) {
|
||||
sb.append("_");
|
||||
} else {
|
||||
if (Character.isWhitespace(buffer.charAt(i))) {
|
||||
sb.append(".");
|
||||
} else {
|
||||
sb.append(buffer.charAt(i));
|
||||
}
|
||||
}
|
||||
if (i == cursor) {
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
sb.append(" >> [").append(Joiner.on(",").join(candidates)).append("]");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue