Query Syntax
The query syntax is based on Lucene query syntax and is compatible with Elasticsearch query_string.
Basic Queries
Term Queries
Match documents where a field contains a specific value:
| Syntax | Description | Example |
|---|---|---|
field:value | Exact match | status:active |
field:"quoted value" | Exact phrase match | name:"John Smith" |
value | Search default fields | error |
var parser = new LuceneQueryParser();
// Simple term
var result = parser.Parse("status:active");
// Quoted phrase
result = parser.Parse("name:\"John Smith\"");
// Default field search (when configured)
result = parser.Parse("error");Existence Queries
Check if a field has any value or is missing:
| Syntax | Description |
|---|---|
_exists_:field | Field has any value |
_missing_:field | Field has no value (null or missing) |
// Find documents with a title
var result = parser.Parse("_exists_:title");
// Find documents without a description
result = parser.Parse("_missing_:description");Wildcard Queries
Use wildcards for partial matching:
| Wildcard | Description | Example |
|---|---|---|
* | Matches zero or more characters | name:john* |
? | Matches exactly one character | name:jo?n |
// Prefix match
var result = parser.Parse("name:john*");
// Single character wildcard
result = parser.Parse("code:A?123");Leading Wildcards
Leading wildcards (*value) can be expensive. Use validation options to disable them if needed.
Regex Queries
Use regular expressions enclosed in forward slashes:
field:/regex/Example:
// Match email patterns
var result = parser.Parse("email:/.*@example\\.com/");Range Queries
Range queries filter numeric or date fields within bounds.
Bracket Syntax
| Syntax | Description |
|---|---|
[min TO max] | Inclusive on both ends |
{min TO max} | Exclusive on both ends |
[min TO max} | Inclusive min, exclusive max |
{min TO max] | Exclusive min, inclusive max |
Examples:
// Inclusive range: 1 <= value <= 5
var result = parser.Parse("field:[1 TO 5]");
// Exclusive range: 1 < value < 5
result = parser.Parse("field:{1 TO 5}");
// Mixed: 1 <= value < 5
result = parser.Parse("field:[1 TO 5}");Shorthand Syntax
Use .. as shorthand for inclusive ranges:
// Equivalent to field:[1 TO 5]
var result = parser.Parse("field:1..5");Unbounded Ranges
Use * for unbounded sides:
// All values before 2024
var result = parser.Parse("date:{* TO 2024-01-01}");
// All values 10 and above
result = parser.Parse("count:[10 TO *]");Comparison Operators
Use comparison operators for single-sided ranges:
| Operator | Description | Equivalent |
|---|---|---|
> | Greater than | {value TO *} |
>= | Greater than or equal | [value TO *] |
< | Less than | {* TO value} |
<= | Less than or equal | {* TO value] |
// Greater than 10
var result = parser.Parse("age:>10");
// Greater than or equal to 10
result = parser.Parse("age:>=10");
// Less than 100
result = parser.Parse("price:<100");
// Less than or equal to 100
result = parser.Parse("price:<=100");Boolean Operators
Combine queries with boolean logic:
| Operator | Description | Alternative |
|---|---|---|
AND | Both conditions must match | && |
OR | Either condition must match | || |
NOT | Negate the following condition | ! |
Examples
// AND - both must match
var result = parser.Parse("status:active AND type:user");
// OR - either must match
result = parser.Parse("status:active OR status:pending");
// NOT - exclude matches
result = parser.Parse("status:active AND NOT deleted:true");
// Complex boolean
result = parser.Parse("((status:active AND type:user) OR type:admin) AND NOT deleted:true");Prefix Operators
Use prefix operators for required/excluded terms:
| Prefix | Description |
|---|---|
+ | Term must be present (required) |
- | Term must not be present (excluded) |
! | Term must not be present (excluded) |
// Required term
var result = parser.Parse("+status:active");
// Excluded term
result = parser.Parse("-deleted:true");
// Combined
result = parser.Parse("+status:active -deleted:true type:user");-, !, and NOT all negate a clause, but the parser stores them on different node properties: NOT sets IsNegated while - and ! set Prefix. In query contexts, use the IsExcluded() extension method rather than checking either property directly. See Negation and Prefix Operators.
These operators may be written either before the field name or immediately after the colon, and both forms are equivalent: -field:value and field:-value parse the same, as do -field:(value) and field:-(value). The one exception is ranges, where only the leading position is accepted -- -field:[1 TO 2] and NOT field:[1 TO 2] work, while field:-[1 TO 2] and field:NOT [1 TO 2] throw a FormatException.
Grouping
Use parentheses to group clauses and control precedence:
// Group OR conditions
var result = parser.Parse("(status:active OR status:pending) AND type:user");
// Nested groups
result = parser.Parse("((a:1 OR b:2) AND c:3) OR d:4");Field Grouping
Apply a field to multiple values:
// Field applies to all terms in group
var result = parser.Parse("status:(active OR pending OR review)");Date Math
Date fields support date math expressions for relative dates.
Anchor Date
Expressions start with an anchor:
now- Current date/time2024-01-01||- Specific date followed by||
Math Operations
| Operation | Description |
|---|---|
+1d | Add 1 day |
-1d | Subtract 1 day |
/d | Round down to day |
Supported Units
| Unit | Description |
|---|---|
y | Years |
M | Months |
w | Weeks |
d | Days |
h or H | Hours |
m | Minutes |
s | Seconds |
Examples
Assuming current time is 2024-06-15 12:00:00:
| Expression | Result |
|---|---|
now | 2024-06-15 12:00:00 |
now+1h | 2024-06-15 13:00:00 |
now-1d | 2024-06-14 12:00:00 |
now-7d | 2024-06-08 12:00:00 |
now/d | 2024-06-15 00:00:00 |
now-1M/M | 2024-05-01 00:00:00 |
// Last 7 days
var result = parser.Parse("created:[now-7d TO now]");
// Last month
result = parser.Parse("created:[now-1M/M TO now/M]");
// Future dates
result = parser.Parse("expires:[now TO now+30d]");Geo Proximity Queries
Filter documents by geographic distance from a point.
Syntax
geofield:location~distanceWhere:
locationcan be a geohash, coordinates, or resolvable location (zip code, city)distanceis a number followed by a unit (mi,km,m)
Examples
// Within 75 miles of a geohash
var result = parser.Parse("location:abc123~75mi");
// Within 75 miles of a zip code (requires geo resolver)
result = parser.Parse("location:75044~75mi");
// Within 10 kilometers
result = parser.Parse("location:51.5,-0.1~10km");Configuration
Geo queries require a location resolver:
var parser = new ElasticQueryParser(c => c
.UseGeo(location => {
// Resolve location string to coordinates
if (location == "75044")
return "32.9,-96.8";
return location;
}));Geo Range Queries
Filter documents within a geographic bounding box.
Syntax
geofield:[topLeft TO bottomRight]Examples
// Bounding box with geohashes
var result = parser.Parse("location:[u4pruydqqv TO u4pruydr2n]");
// Bounding box with coordinates
result = parser.Parse("location:[51.5,-0.2 TO 51.4,-0.1]");Nested Document Queries
When using Elasticsearch, queries on nested document fields work automatically with the ElasticQueryParser:
var parser = new ElasticQueryParser(c => c
.UseMappings(client, "my-index")
.UseNested());
// Query nested field - automatically wrapped in nested query
var query = await parser.BuildQueryAsync("comments.author:john");
// Grouped nested query
query = await parser.BuildQueryAsync("comments:(comments.author:john comments.text:hello)");
// Negated nested query
query = await parser.BuildQueryAsync("NOT comments:(comments.author:spammer)");
// Exists/missing on nested fields
query = await parser.BuildQueryAsync("_exists_:comments.author");
query = await parser.BuildQueryAsync("_missing_:comments");Elasticsearch Limitation
Standard Elasticsearch query_string does not support nested documents. Foundatio.Parsers automatically detects nested fields and wraps queries appropriately, including support for negation, exists/missing, wildcards, and sorting.
For a full explanation of how the AST is structured and traversed for nested queries, see Nested Queries and Visitor Traversal.
Boosting
Boost the relevance of specific terms:
// Boost a term
var result = parser.Parse("title:important^2");
// Boost a phrase
result = parser.Parse("title:\"very important\"^3");Fuzzy Queries
Use ~ for fuzzy matching (edit distance):
// Fuzzy match with default edit distance
var result = parser.Parse("name:john~");
// Fuzzy match with specific edit distance
result = parser.Parse("name:john~2");Escaping Special Characters
Escape special characters with backslash:
+ - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /// Escape colon in value
var result = parser.Parse("url:https\\://example.com");
// Escape parentheses
result = parser.Parse("name:John\\ \\(Jr\\)");Complete Example
using Foundatio.Parsers.LuceneQueries;
using Foundatio.Parsers.LuceneQueries.Visitors;
var parser = new LuceneQueryParser();
// Complex query
string query = @"
(status:active OR status:pending)
AND created:[now-30d TO now]
AND NOT deleted:true
AND (name:john* OR email:*@example.com)
";
var result = parser.Parse(query);
// Debug the AST
Console.WriteLine(DebugQueryVisitor.Run(result));
// Regenerate (normalized)
string normalized = GenerateQueryVisitor.Run(result);Next Steps
- Aggregation Syntax - Dynamic aggregation expressions
- Field Aliases - Map field names
- Validation - Validate and restrict queries