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 {