From de0f8412bddb5459550f9ea00bf2ecd295e22134 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sun, 25 Mar 2018 19:17:49 +0200 Subject: [PATCH] show proposals for empty terminals --- .../org/lucares/pdb/datastore/lang/PdbLang.g4 | 2 ++ .../pdb/datastore/internal/Proposer.java | 20 ++++++++++++++++--- .../lang/QueryCompletionPdbLangParser.java | 19 +++++++++++------- .../pdb/datastore/internal/ProposerTest.java | 17 ++++++++++++++++ 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/data-store/src/main/antlr/org/lucares/pdb/datastore/lang/PdbLang.g4 b/data-store/src/main/antlr/org/lucares/pdb/datastore/lang/PdbLang.g4 index 35add51..067b7b8 100644 --- a/data-store/src/main/antlr/org/lucares/pdb/datastore/lang/PdbLang.g4 +++ b/data-store/src/main/antlr/org/lucares/pdb/datastore/lang/PdbLang.g4 @@ -39,6 +39,7 @@ fragment JavaLetter : [a-zA-Z0-9$_] // these are the "java letters" below 0x7F | [\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 ~[\u0000-\u007F\uD800-\uDBFF] {Character.isJavaIdentifierStart(_input.LA(-1))}? @@ -51,6 +52,7 @@ fragment JavaLetterOrDigit : [a-zA-Z0-9$_] // these are the "java letters or digits" below 0x7F | [\u002a] // asterisk, used for wildcards + | [\ue001] // used to help parser identify empty identifiers (character is the second in the private use area) | '.' | '/' | '-' diff --git a/data-store/src/main/java/org/lucares/pdb/datastore/internal/Proposer.java b/data-store/src/main/java/org/lucares/pdb/datastore/internal/Proposer.java index 349d91d..54707d6 100644 --- a/data-store/src/main/java/org/lucares/pdb/datastore/internal/Proposer.java +++ b/data-store/src/main/java/org/lucares/pdb/datastore/internal/Proposer.java @@ -13,6 +13,16 @@ import org.lucares.utils.CollectionUtils; 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. + *

+ * 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; public Proposer(final DataStore dataStore) { @@ -25,10 +35,14 @@ public class Proposer { if (StringUtils.isBlank(query)) { result = proposeForAllKeys(); } 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 proposeForAllKeys() { @@ -40,7 +54,7 @@ public class Proposer { result = toProposalsForQueries(fieldToQuery); return result; } - + private SortedSet toProposalsForQueries(final Map keyToQuery) { final SortedSet result = new TreeSet<>(); diff --git a/data-store/src/main/java/org/lucares/pdb/datastore/lang/QueryCompletionPdbLangParser.java b/data-store/src/main/java/org/lucares/pdb/datastore/lang/QueryCompletionPdbLangParser.java index 04c5e47..e14d8c8 100644 --- a/data-store/src/main/java/org/lucares/pdb/datastore/lang/QueryCompletionPdbLangParser.java +++ b/data-store/src/main/java/org/lucares/pdb/datastore/lang/QueryCompletionPdbLangParser.java @@ -19,6 +19,7 @@ import org.antlr.v4.runtime.tree.ErrorNode; import org.antlr.v4.runtime.tree.TerminalNode; import org.lucares.pdb.datastore.Proposal; import org.lucares.pdb.datastore.internal.DataStore; +import org.lucares.pdb.datastore.internal.Proposer; import org.lucares.utils.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +27,7 @@ import org.slf4j.LoggerFactory; public class QueryCompletionPdbLangParser extends PdbLangParser { private static final Logger LOGGER = LoggerFactory.getLogger(QueryCompletionPdbLangParser.class); - + public class Listener implements PdbLangListener, ANTLRErrorListener { private final int caretPosition; @@ -52,7 +53,8 @@ public class QueryCompletionPdbLangParser extends PdbLangParser { if (_ctx instanceof PropertyTerminalExpressionContext) { 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 proposedValues = getPropertyValuesByPrefix(propertyKey, propertyValuePrefix); @@ -66,20 +68,23 @@ public class QueryCompletionPdbLangParser extends PdbLangParser { }).map(p -> { final int count = dataStore.count(p.getProposedQuery()); - return new Proposal(p, count> 0); + return new Proposal(p, count > 0); }).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) { 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); newQueryPattern.replace(start, end + 1, "%s"); 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); } } } diff --git a/data-store/src/test/java/org/lucares/pdb/datastore/internal/ProposerTest.java b/data-store/src/test/java/org/lucares/pdb/datastore/internal/ProposerTest.java index cdb8f57..92d196f 100644 --- a/data-store/src/test/java/org/lucares/pdb/datastore/internal/ProposerTest.java +++ b/data-store/src/test/java/org/lucares/pdb/datastore/internal/ProposerTest.java @@ -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) throws InterruptedException {