show proposals for empty terminals
This commit is contained in:
@@ -39,6 +39,7 @@ fragment
|
|||||||
JavaLetter
|
JavaLetter
|
||||||
: [a-zA-Z0-9$_] // these are the "java letters" below 0x7F
|
: [a-zA-Z0-9$_] // these are the "java letters" below 0x7F
|
||||||
| [\u002a] // asterisk, used for wildcards
|
| [\u002a] // asterisk, used for wildcards
|
||||||
|
| [\ue001] // used to help parser identify empty identifiers (character is the second in the private use area)
|
||||||
| // covers all characters above 0x7F which are not a surrogate
|
| // covers all characters above 0x7F which are not a surrogate
|
||||||
~[\u0000-\u007F\uD800-\uDBFF]
|
~[\u0000-\u007F\uD800-\uDBFF]
|
||||||
{Character.isJavaIdentifierStart(_input.LA(-1))}?
|
{Character.isJavaIdentifierStart(_input.LA(-1))}?
|
||||||
@@ -51,6 +52,7 @@ fragment
|
|||||||
JavaLetterOrDigit
|
JavaLetterOrDigit
|
||||||
: [a-zA-Z0-9$_] // these are the "java letters or digits" below 0x7F
|
: [a-zA-Z0-9$_] // these are the "java letters or digits" below 0x7F
|
||||||
| [\u002a] // asterisk, used for wildcards
|
| [\u002a] // asterisk, used for wildcards
|
||||||
|
| [\ue001] // used to help parser identify empty identifiers (character is the second in the private use area)
|
||||||
| '.'
|
| '.'
|
||||||
| '/'
|
| '/'
|
||||||
| '-'
|
| '-'
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ import org.lucares.utils.CollectionUtils;
|
|||||||
|
|
||||||
public class Proposer {
|
public class Proposer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We are using an AntLR parser to find the right proposals. But that approach
|
||||||
|
* does not work if a terminal is empty. The parser does not recognize missing
|
||||||
|
* terminals.
|
||||||
|
* <p>
|
||||||
|
* The hack-around is to add a marker character that helps the parser to find
|
||||||
|
* the terminal.
|
||||||
|
*/
|
||||||
|
public static final String PREFIX_MARKER = "\ue001"; // second character in the private use area
|
||||||
|
|
||||||
private final DataStore dataStore;
|
private final DataStore dataStore;
|
||||||
|
|
||||||
public Proposer(final DataStore dataStore) {
|
public Proposer(final DataStore dataStore) {
|
||||||
@@ -25,10 +35,14 @@ public class Proposer {
|
|||||||
if (StringUtils.isBlank(query)) {
|
if (StringUtils.isBlank(query)) {
|
||||||
result = proposeForAllKeys();
|
result = proposeForAllKeys();
|
||||||
} else {
|
} else {
|
||||||
result = ProposerParser.parse(query, dataStore, caretIndex);
|
|
||||||
|
final StringBuilder q = new StringBuilder(query);
|
||||||
|
q.insert(caretIndex, PREFIX_MARKER);
|
||||||
|
|
||||||
|
result = ProposerParser.parse(q.toString(), dataStore, caretIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CollectionUtils.filter(result, p -> p.hasResults() );
|
return CollectionUtils.filter(result, p -> p.hasResults());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SortedSet<Proposal> proposeForAllKeys() {
|
private SortedSet<Proposal> proposeForAllKeys() {
|
||||||
@@ -40,7 +54,7 @@ public class Proposer {
|
|||||||
result = toProposalsForQueries(fieldToQuery);
|
result = toProposalsForQueries(fieldToQuery);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SortedSet<Proposal> toProposalsForQueries(final Map<String, String> keyToQuery) {
|
private SortedSet<Proposal> toProposalsForQueries(final Map<String, String> keyToQuery) {
|
||||||
|
|
||||||
final SortedSet<Proposal> result = new TreeSet<>();
|
final SortedSet<Proposal> result = new TreeSet<>();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import org.antlr.v4.runtime.tree.ErrorNode;
|
|||||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||||
import org.lucares.pdb.datastore.Proposal;
|
import org.lucares.pdb.datastore.Proposal;
|
||||||
import org.lucares.pdb.datastore.internal.DataStore;
|
import org.lucares.pdb.datastore.internal.DataStore;
|
||||||
|
import org.lucares.pdb.datastore.internal.Proposer;
|
||||||
import org.lucares.utils.CollectionUtils;
|
import org.lucares.utils.CollectionUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -26,7 +27,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
public class QueryCompletionPdbLangParser extends PdbLangParser {
|
public class QueryCompletionPdbLangParser extends PdbLangParser {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(QueryCompletionPdbLangParser.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(QueryCompletionPdbLangParser.class);
|
||||||
|
|
||||||
public class Listener implements PdbLangListener, ANTLRErrorListener {
|
public class Listener implements PdbLangListener, ANTLRErrorListener {
|
||||||
|
|
||||||
private final int caretPosition;
|
private final int caretPosition;
|
||||||
@@ -52,7 +53,8 @@ public class QueryCompletionPdbLangParser extends PdbLangParser {
|
|||||||
|
|
||||||
if (_ctx instanceof PropertyTerminalExpressionContext) {
|
if (_ctx instanceof PropertyTerminalExpressionContext) {
|
||||||
final String propertyKey = _ctx.getParent().children.get(0).getText();
|
final String propertyKey = _ctx.getParent().children.get(0).getText();
|
||||||
final String propertyValuePrefix = node.getText().substring(0, caretPosition - start);
|
String propertyValuePrefix = node.getText().substring(0, caretPosition - start);
|
||||||
|
propertyValuePrefix = propertyValuePrefix.replace(Proposer.PREFIX_MARKER, "");
|
||||||
final SortedSet<String> proposedValues = getPropertyValuesByPrefix(propertyKey,
|
final SortedSet<String> proposedValues = getPropertyValuesByPrefix(propertyKey,
|
||||||
propertyValuePrefix);
|
propertyValuePrefix);
|
||||||
|
|
||||||
@@ -66,20 +68,23 @@ public class QueryCompletionPdbLangParser extends PdbLangParser {
|
|||||||
}).map(p -> {
|
}).map(p -> {
|
||||||
|
|
||||||
final int count = dataStore.count(p.getProposedQuery());
|
final int count = dataStore.count(p.getProposedQuery());
|
||||||
return new Proposal(p, count> 0);
|
return new Proposal(p, count > 0);
|
||||||
}).forEach(proposals::add);
|
}).forEach(proposals::add);
|
||||||
|
|
||||||
LOGGER.trace("proposals for property value {} took {} ms", propertyValuePrefix, (System.nanoTime() - startTime) / 1_000_000.0);
|
LOGGER.trace("proposals for property value {} took {} ms", propertyValuePrefix,
|
||||||
|
(System.nanoTime() - startTime) / 1_000_000.0);
|
||||||
} else if (_ctx instanceof IdentifierExpressionContext) {
|
} else if (_ctx instanceof IdentifierExpressionContext) {
|
||||||
final long startTime = System.nanoTime();
|
final long startTime = System.nanoTime();
|
||||||
final String propertyKeyPrefix = node.getText().substring(0, caretPosition - start);
|
String propertyKeyPrefix = node.getText().substring(0, caretPosition - start);
|
||||||
|
propertyKeyPrefix = propertyKeyPrefix.replace(Proposer.PREFIX_MARKER, "");
|
||||||
|
|
||||||
final StringBuilder newQueryPattern = new StringBuilder(query);
|
final StringBuilder newQueryPattern = new StringBuilder(query);
|
||||||
newQueryPattern.replace(start, end + 1, "%s");
|
newQueryPattern.replace(start, end + 1, "%s");
|
||||||
|
|
||||||
addProposalsForKeys(propertyKeyPrefix, newQueryPattern.toString());
|
addProposalsForKeys(propertyKeyPrefix, newQueryPattern.toString());
|
||||||
|
|
||||||
LOGGER.trace("proposals for property key {} took {} ms", propertyKeyPrefix, (System.nanoTime() - startTime) / 1_000_000.0);
|
LOGGER.trace("proposals for property key {} took {} ms", propertyKeyPrefix,
|
||||||
|
(System.nanoTime() - startTime) / 1_000_000.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,23 @@ public class ProposerTest {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testProposalOnEmptyValuePrefix() throws Exception {
|
||||||
|
assertProposals("name=", 5, //
|
||||||
|
new Proposal("Jennifer", "name=Jennifer ", true), //
|
||||||
|
new Proposal("Jenny", "name=Jenny ", true), //
|
||||||
|
new Proposal("Tim", "name=Tim ", true), //
|
||||||
|
new Proposal("Timothy", "name=Timothy ", true) //
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testProposalOnEmptyKeyPrefix() throws Exception {
|
||||||
|
assertProposals("name=* and ", 11, //
|
||||||
|
new Proposal("name", "name=* and name=* ", true), //
|
||||||
|
new Proposal("bird", "name=* and bird=* ", true), //
|
||||||
|
new Proposal("dog", "name=* and dog=* ", true) //
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertProposals(final String query, final int caretIndex, final Proposal... expected)
|
private void assertProposals(final String query, final int caretIndex, final Proposal... expected)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user