introduce clustering for query completion indices
This commit is contained in:
@@ -14,6 +14,10 @@ public class ClusterId {
|
||||
this.clusterId = clusterId;
|
||||
}
|
||||
|
||||
public static ClusterId of(final String clusterId) {
|
||||
return new ClusterId(clusterId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id, e.g. a time like 201902 (cluster for entries of February
|
||||
* 2019)
|
||||
|
||||
@@ -45,10 +45,18 @@ public class ClusteredPersistentMap<K, V> implements AutoCloseable {
|
||||
};
|
||||
}
|
||||
|
||||
private PersistentMap<K, V> getExistingPersistentMap(final ClusterId clusterId) {
|
||||
return maps.computeIfAbsent(clusterId, supplier);
|
||||
}
|
||||
|
||||
private PersistentMap<K, V> getPersistentMapCreateIfNotExists(final ClusterId clusterId) {
|
||||
return maps.computeIfAbsent(clusterId, creator);
|
||||
}
|
||||
|
||||
public V getValue(final ClusterId clusterId, final K key) {
|
||||
try {
|
||||
|
||||
final PersistentMap<K, V> map = maps.computeIfAbsent(clusterId, supplier);
|
||||
final PersistentMap<K, V> map = getExistingPersistentMap(clusterId);
|
||||
return map != null ? map.getValue(key) : null;
|
||||
} catch (final IOException e) {
|
||||
throw new ReadRuntimeException(e);
|
||||
@@ -61,7 +69,7 @@ public class ClusteredPersistentMap<K, V> implements AutoCloseable {
|
||||
final List<ClusterId> clusterIds = clusterIdSource.toClusterIds();
|
||||
|
||||
for (final ClusterId clusterId : clusterIds) {
|
||||
final PersistentMap<K, V> map = maps.computeIfAbsent(clusterId, creator);
|
||||
final PersistentMap<K, V> map = getPersistentMapCreateIfNotExists(clusterId);
|
||||
if (map != null) {
|
||||
final V value = map.getValue(key);
|
||||
if (value != null) {
|
||||
@@ -79,7 +87,7 @@ public class ClusteredPersistentMap<K, V> implements AutoCloseable {
|
||||
public V putValue(final ClusterId clusterId, final K key, final V value) {
|
||||
try {
|
||||
|
||||
final PersistentMap<K, V> map = maps.computeIfAbsent(clusterId, creator);
|
||||
final PersistentMap<K, V> map = getPersistentMapCreateIfNotExists(clusterId);
|
||||
return map.putValue(key, value);
|
||||
} catch (final IOException e) {
|
||||
throw new ReadRuntimeException(e);
|
||||
@@ -88,7 +96,7 @@ public class ClusteredPersistentMap<K, V> implements AutoCloseable {
|
||||
|
||||
public void visitValues(final ClusterId clusterId, final K keyPrefix, final Visitor<K, V> visitor) {
|
||||
try {
|
||||
final PersistentMap<K, V> map = maps.computeIfAbsent(clusterId, creator);
|
||||
final PersistentMap<K, V> map = getExistingPersistentMap(clusterId);
|
||||
if (map != null) {
|
||||
map.visitValues(keyPrefix, visitor);
|
||||
}
|
||||
@@ -102,7 +110,7 @@ public class ClusteredPersistentMap<K, V> implements AutoCloseable {
|
||||
final List<ClusterId> clusterIds = clusterIdSource.toClusterIds();
|
||||
|
||||
for (final ClusterId clusterId : clusterIds) {
|
||||
final PersistentMap<K, V> map = maps.get(clusterId);
|
||||
final PersistentMap<K, V> map = getExistingPersistentMap(clusterId);
|
||||
if (map != null) {
|
||||
map.visitValues(keyPrefix, visitor);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.util.function.Consumer;
|
||||
import org.lucares.collections.LongList;
|
||||
import org.lucares.pdb.api.DateTimeRange;
|
||||
import org.lucares.pdb.api.Query;
|
||||
import org.lucares.pdb.api.QueryWithCaretMarker;
|
||||
import org.lucares.pdb.api.RuntimeIOException;
|
||||
import org.lucares.pdb.api.StringCompressor;
|
||||
import org.lucares.pdb.api.Tag;
|
||||
@@ -159,7 +160,7 @@ public class DataStore implements AutoCloseable {
|
||||
|
||||
// index the tags, so that we can efficiently find all possible values for a
|
||||
// field in a query
|
||||
queryCompletionIndex.addTags(tags);
|
||||
queryCompletionIndex.addTags(clusterId, tags);
|
||||
|
||||
return newFilesRootBlockOffset;
|
||||
} catch (final IOException e) {
|
||||
@@ -343,10 +344,10 @@ public class DataStore implements AutoCloseable {
|
||||
});
|
||||
}
|
||||
|
||||
public List<Proposal> propose(final String query, final int caretIndex) {
|
||||
public List<Proposal> propose(final QueryWithCaretMarker query) {
|
||||
|
||||
final NewProposerParser newProposerParser = new NewProposerParser(queryCompletionIndex);
|
||||
final List<Proposal> proposals = newProposerParser.propose(query, caretIndex);
|
||||
final List<Proposal> proposals = newProposerParser.propose(query);
|
||||
LOGGER.debug("Proposals for query {}: {}", query, proposals);
|
||||
return proposals;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.lucares.collections.LongList;
|
||||
import org.lucares.pdb.api.RuntimeIOException;
|
||||
import org.lucares.pdb.api.DateTimeRange;
|
||||
import org.lucares.pdb.api.Tag;
|
||||
import org.lucares.pdb.api.Tags;
|
||||
import org.lucares.pdb.map.Empty;
|
||||
@@ -208,22 +208,22 @@ public class QueryCompletionIndex implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private final PersistentMap<TwoTags, Empty> tagToTagIndex;
|
||||
private final PersistentMap<Tag, Empty> fieldToValueIndex;
|
||||
private final PersistentMap<String, Empty> fieldIndex;
|
||||
private final ClusteredPersistentMap<TwoTags, Empty> tagToTagIndex;
|
||||
private final ClusteredPersistentMap<Tag, Empty> fieldToValueIndex;
|
||||
private final ClusteredPersistentMap<String, Empty> fieldIndex;
|
||||
|
||||
public QueryCompletionIndex(final Path basePath) throws IOException {
|
||||
final Path tagToTagIndexFile = basePath.resolve("queryCompletionTagToTagIndex.bs");
|
||||
tagToTagIndex = new PersistentMap<>(tagToTagIndexFile, new EncoderTwoTags(), PersistentMap.EMPTY_ENCODER);
|
||||
tagToTagIndex = new ClusteredPersistentMap<>(basePath, "queryCompletionTagToTagIndex.bs", new EncoderTwoTags(),
|
||||
PersistentMap.EMPTY_ENCODER);
|
||||
|
||||
final Path fieldToValueIndexFile = basePath.resolve("queryCompletionFieldToValueIndex.bs");
|
||||
fieldToValueIndex = new PersistentMap<>(fieldToValueIndexFile, new EncoderTag(), PersistentMap.EMPTY_ENCODER);
|
||||
fieldToValueIndex = new ClusteredPersistentMap<>(basePath, "queryCompletionFieldToValueIndex.bs",
|
||||
new EncoderTag(), PersistentMap.EMPTY_ENCODER);
|
||||
|
||||
final Path fieldIndexFile = basePath.resolve("queryCompletionFieldIndex.bs");
|
||||
fieldIndex = new PersistentMap<>(fieldIndexFile, new EncoderField(), PersistentMap.EMPTY_ENCODER);
|
||||
fieldIndex = new ClusteredPersistentMap<>(basePath, "queryCompletionFieldIndex.bs", new EncoderField(),
|
||||
PersistentMap.EMPTY_ENCODER);
|
||||
}
|
||||
|
||||
public void addTags(final Tags tags) throws IOException {
|
||||
public void addTags(final ClusterId clusterId, final Tags tags) throws IOException {
|
||||
final List<Tag> listOfTagsA = tags.toTags();
|
||||
final List<Tag> listOfTagsB = tags.toTags();
|
||||
|
||||
@@ -231,14 +231,14 @@ public class QueryCompletionIndex implements AutoCloseable {
|
||||
for (final Tag tagA : listOfTagsA) {
|
||||
for (final Tag tagB : listOfTagsB) {
|
||||
final TwoTags key = new TwoTags(tagA, tagB);
|
||||
tagToTagIndex.putValue(key, Empty.INSTANCE);
|
||||
tagToTagIndex.putValue(clusterId, key, Empty.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
// create indices of all tags and all fields
|
||||
for (final Tag tag : listOfTagsA) {
|
||||
fieldToValueIndex.putValue(tag, Empty.INSTANCE);
|
||||
fieldIndex.putValue(tag.getKeyAsString(), Empty.INSTANCE);
|
||||
fieldToValueIndex.putValue(clusterId, tag, Empty.INSTANCE);
|
||||
fieldIndex.putValue(clusterId, tag.getKeyAsString(), Empty.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,76 +247,67 @@ public class QueryCompletionIndex implements AutoCloseable {
|
||||
tagToTagIndex.close();
|
||||
}
|
||||
|
||||
public SortedSet<String> find(final String property, final String value, final String field) {
|
||||
public SortedSet<String> find(final DateTimeRange dateRange, final String property, final String value,
|
||||
final String field) {
|
||||
final Tag tag = new Tag(property, value);
|
||||
return find(tag, field);
|
||||
return find(dateRange, tag, field);
|
||||
}
|
||||
|
||||
public SortedSet<String> find(final Tag tag, final String field) {
|
||||
try {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final int tagBKey = Tags.STRING_COMPRESSOR.put(field);
|
||||
final Tag tagB = new Tag(tagBKey, -1); // the value must be negative for the prefix search to work. See
|
||||
// EncoderTwoTags
|
||||
final TwoTags keyPrefix = new TwoTags(tag, tagB);
|
||||
tagToTagIndex.visitValues(keyPrefix, (k, v) -> {
|
||||
public SortedSet<String> find(final DateTimeRange dateRange, final Tag tag, final String field) {
|
||||
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final int tagBKey = Tags.STRING_COMPRESSOR.put(field);
|
||||
final Tag tagB = new Tag(tagBKey, -1); // the value must be negative for the prefix search to work. See
|
||||
// EncoderTwoTags
|
||||
final TwoTags keyPrefix = new TwoTags(tag, tagB);
|
||||
|
||||
final ClusterIdSource clusterIdSource = new DateCluster(dateRange);
|
||||
tagToTagIndex.visitValues(clusterIdSource, keyPrefix, (k, v) -> {
|
||||
result.add(k.getTagB().getValueAsString());
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public SortedSet<String> findAllValuesForField(final DateTimeRange dateRange, final String field) {
|
||||
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final int tagKey = Tags.STRING_COMPRESSOR.put(field);
|
||||
final Tag keyPrefix = new Tag(tagKey, -1); // the value must be negative for the prefix search to work. See
|
||||
|
||||
final ClusterIdSource clusterIdSource = new DateCluster(dateRange);
|
||||
fieldToValueIndex.visitValues(clusterIdSource, keyPrefix, (k, v) -> {
|
||||
result.add(k.getValueAsString());
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public SortedSet<String> findAllValuesNotForField(final DateTimeRange dateRange, final Tag tag,
|
||||
final String field) {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
|
||||
final TwoTags keyPrefix = new TwoTags(field, tag.getKeyAsString(), null, null);
|
||||
|
||||
final int negatedValueA = tag.getValue();
|
||||
final ClusterIdSource clusterIdSource = new DateCluster(dateRange);
|
||||
tagToTagIndex.visitValues(clusterIdSource, keyPrefix, (k, v) -> {
|
||||
|
||||
final int valueA = k.getTagA().getValue();
|
||||
if (valueA != negatedValueA) {
|
||||
result.add(k.getTagB().getValueAsString());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public SortedSet<String> findAllValuesForField(final String field) {
|
||||
try {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final int tagKey = Tags.STRING_COMPRESSOR.put(field);
|
||||
final Tag keyPrefix = new Tag(tagKey, -1); // the value must be negative for the prefix search to work. See
|
||||
|
||||
fieldToValueIndex.visitValues(keyPrefix, (k, v) -> {
|
||||
result.add(k.getValueAsString());
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
public SortedSet<String> findAllFields(final DateTimeRange dateRange) {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
final ClusterIdSource clusterIdSource = new DateCluster(dateRange);
|
||||
fieldIndex.visitValues(clusterIdSource, "", (k, v) -> {
|
||||
result.add(k);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public SortedSet<String> findAllValuesNotForField(final Tag tag, final String field) {
|
||||
try {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
|
||||
final TwoTags keyPrefix = new TwoTags(field, tag.getKeyAsString(), null, null);
|
||||
|
||||
final int negatedValueA = tag.getValue();
|
||||
|
||||
tagToTagIndex.visitValues(keyPrefix, (k, v) -> {
|
||||
|
||||
final int valueA = k.getTagA().getValue();
|
||||
if (valueA != negatedValueA) {
|
||||
result.add(k.getTagB().getValueAsString());
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SortedSet<String> findAllFields() {
|
||||
try {
|
||||
final SortedSet<String> result = new TreeSet<>();
|
||||
fieldIndex.visitValues("", (k, v) -> {
|
||||
result.add(k);
|
||||
});
|
||||
return result;
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.lucares.pdb.api.DateTimeRange;
|
||||
import org.lucares.pdb.api.Tag;
|
||||
import org.lucares.pdb.datastore.internal.QueryCompletionIndex;
|
||||
import org.lucares.pdb.datastore.lang.Expression.And;
|
||||
@@ -29,8 +30,11 @@ public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<St
|
||||
private static final class AndCaretExpressionVisitor extends ExpressionVisitor<SortedSet<String>> {
|
||||
private final QueryCompletionIndex index;
|
||||
private final String field;
|
||||
private final DateTimeRange dateTimeRange;
|
||||
|
||||
public AndCaretExpressionVisitor(final QueryCompletionIndex queryCompletionIndex, final String field) {
|
||||
public AndCaretExpressionVisitor(final DateTimeRange dateTimeRange,
|
||||
final QueryCompletionIndex queryCompletionIndex, final String field) {
|
||||
this.dateTimeRange = dateTimeRange;
|
||||
index = queryCompletionIndex;
|
||||
this.field = field;
|
||||
}
|
||||
@@ -41,7 +45,7 @@ public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<St
|
||||
final String fieldA = property.getProperty();
|
||||
final String valueA = property.getValue().getValue();
|
||||
|
||||
SortedSet<String> result = index.find(fieldA, valueA, field);
|
||||
final SortedSet<String> result = index.find(dateTimeRange, fieldA, valueA, field);
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", property, (System.nanoTime() - start) / 1_000_000.0);
|
||||
return result;
|
||||
}
|
||||
@@ -53,7 +57,7 @@ public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<St
|
||||
final String property = expression.getProperty();
|
||||
final List<String> values = expression.getValues();
|
||||
for (final String value : values) {
|
||||
final SortedSet<String> candidates = index.find(property, value, field);
|
||||
final SortedSet<String> candidates = index.find(dateTimeRange, property, value, field);
|
||||
result.addAll(candidates);
|
||||
}
|
||||
METRIC_AND_CARET_LOGGER.debug("{}: {}ms", expression, (System.nanoTime() - start) / 1_000_000.0);
|
||||
@@ -119,8 +123,8 @@ public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<St
|
||||
final Property property = (Property) expression.getExpression();
|
||||
final Tag tag = new Tag(property.getProperty(), property.getValueAsString());
|
||||
|
||||
final SortedSet<String> valuesNotForField = index.findAllValuesNotForField(tag, field);
|
||||
final SortedSet<String> valuesForField = index.find(tag, field);
|
||||
final SortedSet<String> valuesNotForField = index.findAllValuesNotForField(dateTimeRange, tag, field);
|
||||
final SortedSet<String> valuesForField = index.find(dateTimeRange, tag, field);
|
||||
final SortedSet<String> valuesOnlyAvailableInField = CollectionUtils.removeAll(valuesForField,
|
||||
valuesNotForField, TreeSet::new);
|
||||
|
||||
@@ -134,7 +138,11 @@ public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<St
|
||||
|
||||
private final QueryCompletionIndex queryCompletionIndex;
|
||||
|
||||
public FindValuesForQueryCompletion(final QueryCompletionIndex queryCompletionIndex) {
|
||||
private final DateTimeRange dateRange;
|
||||
|
||||
public FindValuesForQueryCompletion(final DateTimeRange dateRange,
|
||||
final QueryCompletionIndex queryCompletionIndex) {
|
||||
this.dateRange = dateRange;
|
||||
this.queryCompletionIndex = queryCompletionIndex;
|
||||
}
|
||||
|
||||
@@ -145,7 +153,7 @@ public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<St
|
||||
final String field = property.getProperty();
|
||||
final String value = property.getValue().getValue();
|
||||
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(field);
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(dateRange, field);
|
||||
|
||||
final String valuePrefix = value.substring(0, value.indexOf(NewProposerParser.CARET_MARKER));
|
||||
|
||||
@@ -167,7 +175,7 @@ public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<St
|
||||
final Expression rightHandExpression = expression.getExpression();
|
||||
|
||||
final SortedSet<String> candidateValues = rightHandExpression
|
||||
.visit(new AndCaretExpressionVisitor(queryCompletionIndex, field));
|
||||
.visit(new AndCaretExpressionVisitor(dateRange, queryCompletionIndex, field));
|
||||
|
||||
final TreeSet<String> result = GloblikePattern.filterValues(candidateValues, valuePrefix, TreeSet::new);
|
||||
|
||||
@@ -185,15 +193,15 @@ public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<St
|
||||
final String valuePattern = valueWithCaretMarker.substring(0,
|
||||
valueWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER));
|
||||
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex
|
||||
.findAllValuesForField(caretExpression.getProperty());
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(dateRange,
|
||||
caretExpression.getProperty());
|
||||
final SortedSet<String> valuesForFieldMatchingCaretExpression = GloblikePattern.filterValues(allValuesForField,
|
||||
valuePattern, TreeSet::new);
|
||||
|
||||
final Expression rightHandExpression = expression.getExpression();
|
||||
|
||||
final SortedSet<String> rightHandValues = rightHandExpression
|
||||
.visit(new AndCaretExpressionVisitor(queryCompletionIndex, field));
|
||||
.visit(new AndCaretExpressionVisitor(dateRange, queryCompletionIndex, field));
|
||||
|
||||
if (rightHandValues.size() == 1) {
|
||||
// there is only one alternative and that one must not be chosen
|
||||
@@ -213,7 +221,7 @@ public class FindValuesForQueryCompletion extends ExpressionVisitor<SortedSet<St
|
||||
if (innerExpression instanceof Property) {
|
||||
final long start = System.nanoTime();
|
||||
field = ((Property) innerExpression).getProperty();
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(field);
|
||||
final SortedSet<String> allValuesForField = queryCompletionIndex.findAllValuesForField(dateRange, field);
|
||||
final String valueWithCaretMarker = ((Property) innerExpression).getValue().getValue();
|
||||
final String valuePrefix = valueWithCaretMarker.substring(0,
|
||||
valueWithCaretMarker.indexOf(NewProposerParser.CARET_MARKER));
|
||||
|
||||
@@ -9,20 +9,21 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.lucares.pdb.api.DateTimeRange;
|
||||
import org.lucares.pdb.api.QueryConstants;
|
||||
import org.lucares.pdb.api.QueryWithCaretMarker;
|
||||
import org.lucares.pdb.datastore.Proposal;
|
||||
import org.lucares.pdb.datastore.internal.QueryCompletionIndex;
|
||||
import org.lucares.utils.CollectionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class NewProposerParser {
|
||||
public class NewProposerParser implements QueryConstants {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NewProposerParser.class);
|
||||
|
||||
private final static Logger METRICS_LOGGER_PROPOSE = LoggerFactory.getLogger("org.lucares.metrics.propose");
|
||||
|
||||
public final static String CARET_MARKER = "\ue001"; // character in the private use area
|
||||
|
||||
/*
|
||||
* Regex matching a java identifier without a caret marker. We define it as a
|
||||
* blacklist, because this is easer. The regex is only used <em>after</em> the
|
||||
@@ -36,16 +37,16 @@ public class NewProposerParser {
|
||||
this.queryCompletionIndex = queryCompletionIndex;
|
||||
}
|
||||
|
||||
public List<Proposal> propose(final String query, final int caretIndex) {
|
||||
public List<Proposal> propose(final QueryWithCaretMarker query) {
|
||||
final long start = System.nanoTime();
|
||||
List<Proposal> proposals;
|
||||
if (StringUtils.isBlank(query)) {
|
||||
proposals = proposeForAllKeys();
|
||||
if (StringUtils.isBlank(query.getQuery())) {
|
||||
proposals = proposeForAllKeys(query.getDateRange());
|
||||
} else {
|
||||
|
||||
final List<Proposal> foundProposals = proposalsForValues(query, caretIndex);
|
||||
final List<Proposal> foundProposals = proposalsForValues(query);
|
||||
if (foundProposals.isEmpty()) {
|
||||
proposals = proposalsForNonValues(query, caretIndex);
|
||||
proposals = proposalsForNonValues(query);
|
||||
} else {
|
||||
proposals = foundProposals;
|
||||
}
|
||||
@@ -56,7 +57,7 @@ public class NewProposerParser {
|
||||
return proposals;
|
||||
}
|
||||
|
||||
private List<Proposal> proposalsForNonValues(final String query, final int caretIndex) {
|
||||
private List<Proposal> proposalsForNonValues(final QueryWithCaretMarker query) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
|
||||
/*
|
||||
@@ -66,9 +67,7 @@ public class NewProposerParser {
|
||||
* location in the query (not at the caret position).
|
||||
*/
|
||||
|
||||
final String queryWithCaretMarker = new StringBuilder(query).insert(caretIndex, CARET_MARKER).toString();
|
||||
|
||||
final List<String> tokens = QueryLanguage.getTokens(queryWithCaretMarker);
|
||||
final List<String> tokens = QueryLanguage.getTokens(query.getQueryWithCaretMarker());
|
||||
final int indexTokenWithCaret = CollectionUtils.indexOf(tokens, t -> t.contains(CARET_MARKER));
|
||||
|
||||
if (indexTokenWithCaret > 0) {
|
||||
@@ -78,7 +77,7 @@ public class NewProposerParser {
|
||||
case "and":
|
||||
case "or":
|
||||
case "!":
|
||||
proposals.addAll(proposeForAllKeys(queryWithCaretMarker));
|
||||
proposals.addAll(proposeForAllKeys(query));
|
||||
break;
|
||||
|
||||
case ")":
|
||||
@@ -87,24 +86,25 @@ public class NewProposerParser {
|
||||
break;
|
||||
}
|
||||
} else if (indexTokenWithCaret == 0) {
|
||||
proposals.addAll(proposeForAllKeys(queryWithCaretMarker));
|
||||
proposals.addAll(proposeForAllKeys(query));
|
||||
}
|
||||
|
||||
return proposals;
|
||||
}
|
||||
|
||||
private Collection<? extends Proposal> proposeForAllKeys(final String queryWithCaretMarker) {
|
||||
private Collection<? extends Proposal> proposeForAllKeys(final QueryWithCaretMarker query) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
final String wordPrefix = wordPrefix(queryWithCaretMarker);
|
||||
final String wordPrefix = wordPrefix(query.getQueryWithCaretMarker());
|
||||
|
||||
if (wordPrefix != null) {
|
||||
final SortedSet<String> allFields = queryCompletionIndex.findAllFields();
|
||||
final SortedSet<String> allFields = queryCompletionIndex.findAllFields(query.getDateRange());
|
||||
for (final String field : allFields) {
|
||||
|
||||
if (!field.startsWith(wordPrefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String queryWithCaretMarker = query.getQueryWithCaretMarker();
|
||||
final String proposedQuery = queryWithCaretMarker
|
||||
.replaceAll(REGEX_IDENTIFIER + CARET_MARKER + REGEX_IDENTIFIER, field + "=* ");
|
||||
final String newQueryWithCaretMarker = queryWithCaretMarker
|
||||
@@ -131,10 +131,10 @@ public class NewProposerParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Proposal> proposeForAllKeys() {
|
||||
private List<Proposal> proposeForAllKeys(final DateTimeRange dateRange) {
|
||||
final List<Proposal> proposals = new ArrayList<>();
|
||||
|
||||
final SortedSet<String> allFields = queryCompletionIndex.findAllFields();
|
||||
final SortedSet<String> allFields = queryCompletionIndex.findAllFields(dateRange);
|
||||
for (final String field : allFields) {
|
||||
final String proposedQuery = field + "=*";
|
||||
final String newQuery = field + "=";
|
||||
@@ -146,26 +146,26 @@ public class NewProposerParser {
|
||||
return proposals;
|
||||
}
|
||||
|
||||
List<Proposal> proposalsForValues(final String query, final int caretIndex) {
|
||||
List<Proposal> proposalsForValues(final QueryWithCaretMarker query) {
|
||||
try {
|
||||
// Add caret marker, so that we know where the caret is.
|
||||
// This also makes sure that a query like "name=|" ('|' is the caret) can be
|
||||
// parsed.
|
||||
// Without the caret marker the query would be "name=", which is not a valid
|
||||
// expression.
|
||||
final String queryWithCaretMarker = new StringBuilder(query).insert(caretIndex, CARET_MARKER).toString();
|
||||
final String queryWithCaretMarker = query.getQueryWithCaretMarker();
|
||||
|
||||
// parse the query
|
||||
final Expression expression = QueryLanguageParser.parse(queryWithCaretMarker);
|
||||
|
||||
// normalize it, so that we can use the queryCompletionIndex to search vor
|
||||
// normalize it, so that we can use the queryCompletionIndex to search for
|
||||
// candidate values
|
||||
final QueryCompletionExpressionOptimizer optimizer = new QueryCompletionExpressionOptimizer();
|
||||
final Expression normalizedExpression = optimizer.normalizeExpression(expression);
|
||||
|
||||
// find all candidate values
|
||||
final SortedSet<String> candidateValues = normalizedExpression
|
||||
.visit(new FindValuesForQueryCompletion(queryCompletionIndex));
|
||||
.visit(new FindValuesForQueryCompletion(query.getDateRange(), queryCompletionIndex));
|
||||
|
||||
final SortedSet<String> candidateValuesCutAtDots = cutAtDots(candidateValues, queryWithCaretMarker);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user