Prefix Query¶
Prefix queries match documents where a field starts with a specified prefix. They're ideal for autocomplete functionality.
Basic Usage¶
QueryBuilder.prefixMatchQuery("author", "mart").buildForageQuery()
// Matches: "martin", "martinez", "martha", etc.
How It Works¶
Index terms: ["martin", "martinez", "martha", "mark", "mary"]
Query prefix: "mart"
↓ (prefix expansion)
Matches: ["martin", "martinez", "martha"]
Examples¶
Autocomplete¶
// User types "prog" in search box
String userInput = "prog";
QueryBuilder.prefixMatchQuery("title", userInput).buildForageQuery()
// Matches: "programming", "progress", "program", "progressive"
Author Name Autocomplete¶
// Find authors starting with "jo"
QueryBuilder.prefixMatchQuery("author", "jo").buildForageQuery()
// Matches: "joshua bloch", "john doe", "jonathan swift"
With Boost¶
QueryBuilder.prefixMatchQuery("title", "java")
.boost(1.5f)
.buildForageQuery()
With Result Limit¶
// Autocomplete suggestions (top 5)
QueryBuilder.prefixMatchQuery("title", userInput)
.buildForageQuery(5)
Combining with Other Queries¶
Prefix with Filters¶
// Autocomplete within a category
QueryBuilder.booleanQuery()
.query(QueryBuilder.prefixMatchQuery("title", "prog").build())
.query(QueryBuilder.matchQuery("category", "technology")
.clauseType(ClauseType.FILTER).build())
.clauseType(ClauseType.MUST)
.buildForageQuery(10)
Multi-Field Prefix¶
// Search prefix in multiple fields
QueryBuilder.booleanQuery()
.query(QueryBuilder.prefixMatchQuery("title", prefix).boost(2.0f).build())
.query(QueryBuilder.prefixMatchQuery("author", prefix).build())
.clauseType(ClauseType.SHOULD)
.buildForageQuery()
Prefix OR Exact¶
// Boost exact matches higher than prefix matches
QueryBuilder.booleanQuery()
.query(QueryBuilder.matchQuery("title", term).boost(3.0f).build())
.query(QueryBuilder.prefixMatchQuery("title", term).boost(1.0f).build())
.clauseType(ClauseType.SHOULD)
.buildForageQuery()
Autocomplete Implementation¶
Basic Autocomplete Endpoint¶
public List<String> autocomplete(String prefix, int limit) {
ForageQueryResult<Book> results = engine.search(
QueryBuilder.prefixMatchQuery("title", prefix.toLowerCase())
.buildForageQuery(limit)
);
return results.getMatchingResults().stream()
.map(r -> r.getData().getTitle())
.distinct()
.collect(Collectors.toList());
}
Autocomplete with Ranking¶
public List<String> autocomplete(String prefix, int limit) {
// Rank by popularity/rating
ForageQueryResult<Book> results = engine.search(
QueryBuilder.functionScoreQuery()
.baseQuery(QueryBuilder.prefixMatchQuery("title", prefix).build())
.fieldValueFactor("popularity")
.buildForageQuery(limit)
);
return results.getMatchingResults().stream()
.map(r -> r.getData().getTitle())
.collect(Collectors.toList());
}
Multi-Category Autocomplete¶
public Map<String, List<String>> autocomplete(String prefix) {
Map<String, List<String>> suggestions = new HashMap<>();
// Book titles
suggestions.put("books", autocompleteField("title", prefix, 5));
// Authors
suggestions.put("authors", autocompleteField("author", prefix, 5));
return suggestions;
}
private List<String> autocompleteField(String field, String prefix, int limit) {
return engine.search(
QueryBuilder.prefixMatchQuery(field, prefix).buildForageQuery(limit)
).getMatchingResults().stream()
.map(r -> extractField(r.getData(), field))
.distinct()
.collect(Collectors.toList());
}
Field Type Behavior¶
TextField¶
Prefix matching works on individual tokens:
// Document: new TextField("title", "Java Programming Guide")
// Tokens: ["java", "programming", "guide"]
QueryBuilder.prefixMatchQuery("title", "prog") // ✓ Matches "programming"
QueryBuilder.prefixMatchQuery("title", "java") // ✓ Matches "java"
QueryBuilder.prefixMatchQuery("title", "jav") // ✓ Matches "java"
StringField¶
Prefix matching works on the entire value:
// Document: new StringField("isbn", "978-0134685991")
QueryBuilder.prefixMatchQuery("isbn", "978") // ✓ Matches
QueryBuilder.prefixMatchQuery("isbn", "978-01") // ✓ Matches
QueryBuilder.prefixMatchQuery("isbn", "0134") // ✗ No match (not prefix)
Performance Considerations¶
| Factor | Impact |
|---|---|
| Short prefix (1-2 chars) | Slow (many matches) |
| Long prefix (4+ chars) | Fast (fewer matches) |
| Common prefix | Slower |
Short Prefixes
Very short prefixes (1-2 characters) can be expensive. Consider:
- Requiring minimum prefix length (3+ chars)
- Adding rate limiting
- Caching common prefixes
Best Practices¶
1. Minimum Prefix Length¶
public List<String> autocomplete(String prefix) {
if (prefix.length() < 3) {
return Collections.emptyList(); // Require 3+ chars
}
// ... execute query
}
2. Debounce on Frontend¶
Wait for user to stop typing before sending request:
// Frontend debouncing
let timeout;
input.addEventListener('input', (e) => {
clearTimeout(timeout);
timeout = setTimeout(() => fetchSuggestions(e.target.value), 300);
});
3. Cache Results¶
private final Cache<String, List<String>> autocompleteCache =
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
public List<String> autocomplete(String prefix) {
return autocompleteCache.get(prefix, () -> executeAutocomplete(prefix));
}
Related Queries¶
- Match Query - Full term matching
- Fuzzy Query - Typo-tolerant matching
- Phrase Query - Exact phrase matching