Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#428 auto complete list #3980

Merged
merged 40 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
35233cb
Introduce GIT Build IDs
manticore-projects Feb 26, 2021
2c7cb86
Merge origin/master
manticore-projects Feb 26, 2021
281898b
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Mar 14, 2021
6e47465
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Mar 30, 2021
0616da1
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Apr 15, 2021
212cb2c
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects May 25, 2021
8c03d6c
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects May 26, 2021
34c42e5
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Jun 4, 2021
803974b
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Jun 7, 2021
8079be5
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Jun 18, 2021
10e4dee
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Jul 15, 2021
68aa726
merge
manticore-projects Aug 19, 2021
9b2fd17
merge
manticore-projects Aug 19, 2021
dab786c
merge
manticore-projects Aug 19, 2021
1bdd87c
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Dec 12, 2021
1ba94eb
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Dec 15, 2021
22f1924
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Dec 21, 2021
3e3611b
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Dec 28, 2021
33daf1b
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Apr 12, 2022
2a4c4c8
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Jul 10, 2022
37850b3
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Apr 25, 2023
8d61ebd
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Aug 13, 2023
f1b72f6
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Sep 3, 2023
95b98cd
set JDK target and source level
manticore-projects Sep 5, 2023
d3b63ea
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Sep 19, 2023
92a60d7
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Dec 13, 2023
3fbbb2c
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Dec 17, 2023
16d4222
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Dec 24, 2023
5ce4018
Merge branch 'master' of https://github.com/h2database/h2database
manticore-projects Jan 24, 2024
98bebc5
fix: AutoCompleteList with quoted schemas or table names
manticore-projects Jan 24, 2024
fbb93fa
test: re-enable standard tests
manticore-projects Jan 24, 2024
6f45ab4
test: add tests for explicitly quoted schemas and table names
manticore-projects Jan 24, 2024
b5be13a
doc: fix the description of the assertion
manticore-projects Jan 24, 2024
aadf38a
fix: don't double quote already quoted identifiers
manticore-projects Jan 24, 2024
9e88133
Merge branch 'master' of https://github.com/h2database/h2database int…
manticore-projects Feb 1, 2024
136a77c
feat: avoid `String.toUpper()` and implement Regular Expression based…
manticore-projects Feb 1, 2024
5462527
fix: quote the prefix so we don't break the regex pattern
manticore-projects Feb 1, 2024
b9454a5
feat: better column identifiers as advised by @katzyn
manticore-projects Feb 1, 2024
792da88
Merge branch 'master' of github.com:h2database/h2database into #428Au…
manticore-projects Feb 13, 2024
ae63339
Fix: use `Collator` for case-insensitive String comparison
manticore-projects Feb 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 32 additions & 32 deletions h2/src/main/org/h2/bnf/context/DbContextRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,27 @@ public void accept(BnfVisitor visitor) {

@Override
public boolean autoComplete(Sentence sentence) {
String query = sentence.getQuery(), s = query;
String up = sentence.getQueryUpper();
final String query = sentence.getQuery();
String s = query;
switch (type) {
case SCHEMA: {
DbSchema[] schemas = contents.getSchemas();
String best = null;
DbSchema bestSchema = null;
for (DbSchema schema: schemas) {
String name = StringUtils.toUpperEnglish(schema.name);
if (up.startsWith(name)) {
String name = schema.name;
String quotedName = StringUtils.quoteIdentifier(name);
if (StringUtils.startsWithIgnoringCase(query, name)) {
if (best == null || name.length() > best.length()) {
best = name;
bestSchema = schema;
}
} else if (s.length() == 0 || name.startsWith(up)) {
} else if (StringUtils.startsWith(query, quotedName)) {
if (best == null || name.length() > best.length()) {
best = quotedName;
bestSchema = schema;
}
} else if (s.isEmpty() || StringUtils.startsWithIgnoringCase(name, query) || StringUtils.startsWithIgnoringCase(quotedName, query)) {
if (s.length() < name.length()) {
sentence.add(name, name.substring(s.length()), type);
sentence.add(schema.quotedName + ".",
Expand All @@ -107,18 +113,15 @@ public boolean autoComplete(Sentence sentence) {
String best = null;
DbTableOrView bestTable = null;
for (DbTableOrView table : tables) {
String compare = up;
String name = StringUtils.toUpperEnglish(table.getName());
if (table.getQuotedName().length() > name.length()) {
name = table.getQuotedName();
compare = query;
}
if (compare.startsWith(name)) {
String name = table.getName();
String quotedName = StringUtils.quoteIdentifier(name);

if (StringUtils.startsWithIgnoringCase(query, name) || StringUtils.startsWithIgnoringCase("\"" + query, quotedName)) {
if (best == null || name.length() > best.length()) {
best = name;
bestTable = table;
}
} else if (s.length() == 0 || name.startsWith(compare)) {
} else if (s.isEmpty() || StringUtils.startsWithIgnoringCase(name, query) || StringUtils.startsWithIgnoringCase(quotedName, query)) {
if (s.length() < name.length()) {
sentence.add(table.getQuotedName(),
table.getQuotedName().substring(s.length()),
Expand All @@ -144,16 +147,14 @@ public boolean autoComplete(Sentence sentence) {
if (query.indexOf(' ') < 0) {
break;
}
for (; i < up.length(); i++) {
char ch = up.charAt(i);
if (ch != '_' && !Character.isLetterOrDigit(ch)) {
break;
}
}
if (i == 0) {
int l = query.length(), cp;
if (!Character.isJavaIdentifierStart(cp = query.codePointAt(i)) || cp == '$') {
break;
}
String alias = up.substring(0, i);
while ((i += Character.charCount(cp)) < l && Character.isJavaIdentifierPart(cp = query.codePointAt(i))) {
//
}
String alias = query.substring(0, i);
if (ParserUtil.isKeyword(alias, false)) {
break;
}
Expand All @@ -166,17 +167,17 @@ public boolean autoComplete(Sentence sentence) {
DbTableOrView last = sentence.getLastMatchedTable();
if (last != null && last.getColumns() != null) {
for (DbColumn column : last.getColumns()) {
String compare = up;
String name = StringUtils.toUpperEnglish(column.getName());
String compare = query;
String name = column.getName();
if (column.getQuotedName().length() > name.length()) {
name = column.getQuotedName();
compare = query;
}
if (compare.startsWith(name) && testColumnType(column)) {
if (StringUtils.startsWithIgnoringCase(compare, name) && testColumnType(column)) {
String b = s.substring(name.length());
if (best == null || b.length() < best.length()) {
best = b;
} else if (s.length() == 0 || name.startsWith(compare)) {
} else if (s.isEmpty() || StringUtils.startsWithIgnoringCase(name, compare)) {
if (s.length() < name.length()) {
sentence.add(column.getName(),
column.getName().substring(s.length()),
Expand All @@ -195,15 +196,14 @@ public boolean autoComplete(Sentence sentence) {
continue;
}
for (DbColumn column : table.getColumns()) {
String name = StringUtils.toUpperEnglish(column
.getName());
String name = column.getName();
if (testColumnType(column)) {
if (up.startsWith(name)) {
if (StringUtils.startsWithIgnoringCase(query, name)) {
String b = s.substring(name.length());
if (best == null || b.length() < best.length()) {
best = b;
}
} else if (s.length() == 0 || name.startsWith(up)) {
} else if (s.isEmpty() || StringUtils.startsWithIgnoringCase(name, query)) {
if (s.length() < name.length()) {
sentence.add(column.getName(),
column.getName().substring(s.length()),
Expand Down Expand Up @@ -326,7 +326,7 @@ private static String autoCompleteTableAlias(Sentence sentence,
return s;
}
s = s.substring(alias.length());
if (s.length() == 0) {
if (s.isEmpty()) {
sentence.add(alias + ".", ".", Sentence.CONTEXT);
}
return s;
Expand All @@ -341,15 +341,15 @@ private static String autoCompleteTableAlias(Sentence sentence,
(best == null || tableName.length() > best.length())) {
sentence.setLastMatchedTable(table);
best = tableName;
} else if (s.length() == 0 || tableName.startsWith(alias)) {
} else if (s.isEmpty() || tableName.startsWith(alias)) {
sentence.add(tableName + ".",
tableName.substring(s.length()) + ".",
Sentence.CONTEXT);
}
}
if (best != null) {
s = s.substring(best.length());
if (s.length() == 0) {
if (s.isEmpty()) {
sentence.add(alias + ".", ".", Sentence.CONTEXT);
}
return s;
Expand Down
32 changes: 32 additions & 0 deletions h2/src/main/org/h2/util/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
import java.lang.ref.SoftReference;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.Collator;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.function.IntPredicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.h2.api.ErrorCode;
import org.h2.engine.SysProperties;
Expand Down Expand Up @@ -1383,4 +1387,32 @@ public static String escapeMetaDataPattern(String pattern) {
return replaceAll(pattern, "\\", "\\\\");
}

/**
* Case-sensitive check if a {@param text} starts with a {@param prefix}.
* It only calls {@code String.startsWith()} and is only here for API consistency
*
* @param text the full text starting with a prefix
* @param prefix the full text starting with a prefix
* @return TRUE only if text starts with the prefix
*/
public static boolean startsWith(String text, String prefix) {
return text.startsWith(prefix);
}

/**
* Case-Insensitive check if a {@param text} starts with a {@param prefix}.
*
* @param text the full text starting with a prefix
* @param prefix the full text starting with a prefix
* @return TRUE only if text starts with the prefix
*/
public static boolean startsWithIgnoringCase(String text, String prefix) {
if (text.length() < prefix.length()) {
return false;
} else {
Collator collator = Collator.getInstance();
collator.setStrength(Collator.PRIMARY);
return collator.equals(text.substring(0, prefix.length()), prefix);
}
}
}
27 changes: 26 additions & 1 deletion h2/src/test/org/h2/test/TestBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -749,11 +749,24 @@ protected void assertSmaller(long a, long b) {
* @throws AssertionError if the term was not found
*/
protected void assertContains(String result, String contains) {
if (result.indexOf(contains) < 0) {
if (!result.contains(contains)) {
fail(result + " does not contain: " + contains);
}
}

/**
* Check that a result does not contain the given substring.
*
* @param result the result value
* @param shallNotContain the term that must not appear in the result
* @throws AssertionError if the term has been found
*/
protected void assertNotContaining(String result, String shallNotContain) {
if (result.contains(shallNotContain)) {
fail(result + " still contains: " + shallNotContain);
}
}

/**
* Check that a text starts with the expected characters..
*
Expand Down Expand Up @@ -852,6 +865,18 @@ public void assertNull(Object obj) {
}
}

/**
* Check that the passed String is empty.
*
* @param s the object
* @throws AssertionError if the String is not empty
*/
public void assertEmpty(String s) {
if (s != null && !s.isEmpty()) {
fail("Expected: empty String but got: " + s);
}
}

/**
* Check that the passed object is not null.
*
Expand Down
97 changes: 97 additions & 0 deletions h2/src/test/org/h2/test/server/TestWeb.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public void test() throws Exception {
testServer();
testWebApp();
testIfExists();

testSpecialAutoComplete();
}

private void testServlet() throws Exception {
Expand Down Expand Up @@ -561,6 +563,101 @@ private void testWebApp() throws Exception {
}
}

private void testSpecialAutoComplete() throws Exception {
Server server = new Server();
server.setOut(new PrintStream(new ByteArrayOutputStream()));
server.runTool("-ifNotExists", "-web", "-webPort", "8182",
"-properties", "null", "-tcp", "-tcpPort", "9101");
try {
String url = "http://localhost:8182";
WebClient client;
String result;
client = new WebClient();
result = client.get(url);
client.readSessionId(result);
client.get(url, "login.jsp");

result = client.get(url, "login.do?driver=org.h2.Driver" +
"&url=jdbc:h2:mem:" + getTestName() +
"&user=sa&password=sa&name=_test_");
result = client.get(url, "header.jsp");

result = client.get(url, "query.do?sql=" +
"create schema test_schema;" +
"create schema \"quoted schema\";" +
"create table test_schema.test_table(id int primary key, name varchar);" +
"insert into test_schema.test_table values(1, 'Hello');" +
"create table \"quoted schema\".\"quoted tablename\"(id int primary key, name varchar);");
result = client.get(url, "query.do?sql=create sequence test_schema.test_sequence");
result = client.get(url, "query.do?sql=" +
"create view test_schema.test_view as select * from test");
result = client.get(url, "tables.do");

result = client.get(url, "query.jsp");

// unquoted autoComplete
result = client.get(url, "autoCompleteList.do?query=select * from test_schema.test");
assertContains(StringUtils.urlDecode(result), "test_table");

// this shall succeed, because "TEST_SCHEMA" exists
result = client.get(url, "autoCompleteList.do?query=select * from TEST");
assertContains(StringUtils.urlDecode(result), "test_schema");

// this shall also succeed, because "TEST_SCHEMA" is similar
result = client.get(url, "autoCompleteList.do?query=select * from \"TEST");
assertContains(StringUtils.urlDecode(result), "test_schema");

// this shall succeed, because "TEST_SCHEMA" exists
result = client.get(url, "autoCompleteList.do?query=select * from \"TEST_SCHEMA\".test");
assertContains(StringUtils.urlDecode(result), "test_table");

// this shall succeed, because "TEST_SCHEMA" and "TEST_TABLE exist
result = client.get(url, "autoCompleteList.do?query=select * from \"TEST_SCHEMA\".\"TEST");
assertContains(StringUtils.urlDecode(result), "test_table");

// this shall also succeed, because we want to be lenient on table names
result = client.get(url, "autoCompleteList.do?query=select * from \"TEST_SCHEMA\".\"test");
assertContains(StringUtils.urlDecode(result), "test_table");

// this shall fail, because there is no "test_schema"
result = client.get(url, "autoCompleteList.do?query=select * from \"test_schema\".test");
assertNotContaining(StringUtils.urlDecode(result),"test_table");

// this shall not return any suggestion since there is no "test_schema"
result = client.get(url, "autoCompleteList.do?query=select * from \"test_schema\".");
assertEmpty(StringUtils.urlDecode(result));

// this shall not return anything, because there is no TEST_TABLE1
result = client.get(url, "autoCompleteList.do?query=select * from \"TEST_SCHEMA\".\"test_table1");
assertEmpty(StringUtils.urlDecode(result));

// explicitly quoted schemas
result = client.get(url, "autoCompleteList.do?query=select * from \"quoted");
assertContains(StringUtils.urlDecode(result),"quoted schema");

// explicitly quoted schemas, very lax
result = client.get(url, "autoCompleteList.do?query=select * from quoted");
assertContains(StringUtils.urlDecode(result),"quoted schema");

// explicitly quoted tablenames
result = client.get(url, "autoCompleteList.do?query=select * from \"quoted schema\".\"quoted");
assertContains(StringUtils.urlDecode(result),"quoted tablename");

// explicitly quoted tablename, but lax
result = client.get(url, "autoCompleteList.do?query=select * from \"quoted schema\".QUOTED");
assertContains(StringUtils.urlDecode(result),"quoted tablename");

// this one must fail
result = client.get(url, "autoCompleteList.do?query=select * from \"quoted schema\".QUOTED1");
assertNotContaining(StringUtils.urlDecode(result),"quoted tablename");

result = client.get(url, "logout.do");

} finally {
server.shutdown();
}
}

private void testStartWebServerWithConnection() throws Exception {
String old = System.getProperty(SysProperties.H2_BROWSER);
try {
Expand Down