Skip to content

Commit ef5794d

Browse files
committed
Readme
1 parent 3dfc826 commit ef5794d

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed
File renamed without changes.

README.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,196 @@
11
# Foundatio.LuceneQueryParser
2+
3+
A high-performance Lucene query string parser for .NET that converts query strings into an Abstract Syntax Tree (AST). Supports query transformation via visitors and includes Entity Framework Core integration for generating LINQ expressions.
4+
5+
## Features
6+
7+
- **Full Lucene Query Syntax** - Terms, phrases, fields, ranges, boolean operators, wildcards, regex, and more
8+
- **Elasticsearch Extensions** - Date math expressions (`now-1d`, `2024-01-01||+1M/d`), `_exists_`, `_missing_`
9+
- **Visitor Pattern** - Transform, validate, or analyze queries with composable visitors
10+
- **Round-Trip Capable** - Parse queries to AST and convert back to query strings
11+
- **Entity Framework Integration** - Convert Lucene queries directly to LINQ expressions
12+
- **Error Recovery** - Resilient parser returns partial AST with detailed error information
13+
14+
## Installation
15+
16+
```bash
17+
# Core parser
18+
dotnet add package Foundatio.LuceneQueryParser
19+
20+
# Entity Framework integration (optional)
21+
dotnet add package Foundatio.LuceneQueryParser.EntityFramework
22+
```
23+
24+
## Quick Start
25+
26+
### Basic Parsing
27+
28+
```csharp
29+
using Foundatio.LuceneQueryParser;
30+
31+
var result = LuceneQuery.Parse("title:hello AND status:active");
32+
33+
if (result.IsSuccess)
34+
{
35+
var document = result.Document; // QueryDocument (root AST node)
36+
}
37+
else
38+
{
39+
// Handle errors - partial AST may still be available
40+
foreach (var error in result.Errors)
41+
Console.WriteLine($"Error at {error.Line}:{error.Column}: {error.Message}");
42+
}
43+
```
44+
45+
### Convert AST Back to Query String
46+
47+
```csharp
48+
using Foundatio.LuceneQueryParser;
49+
50+
var result = LuceneQuery.Parse("title:test AND (status:active OR status:pending)");
51+
var queryString = QueryStringBuilder.ToQueryString(result.Document);
52+
// Returns: "title:test AND (status:active OR status:pending)"
53+
```
54+
55+
### Field Aliasing
56+
57+
```csharp
58+
using Foundatio.LuceneQueryParser;
59+
using Foundatio.LuceneQueryParser.Visitors;
60+
61+
var result = LuceneQuery.Parse("user:john AND created:[2020-01-01 TO 2020-12-31]");
62+
63+
var fieldMap = new FieldMap
64+
{
65+
{ "user", "account.username" },
66+
{ "created", "metadata.timestamp" }
67+
};
68+
69+
await FieldResolverQueryVisitor.RunAsync(result.Document, fieldMap);
70+
71+
var resolved = QueryStringBuilder.ToQueryString(result.Document);
72+
// Returns: "account.username:john AND metadata.timestamp:[2020-01-01 TO 2020-12-31]"
73+
```
74+
75+
### Query Validation
76+
77+
```csharp
78+
using Foundatio.LuceneQueryParser;
79+
80+
var result = LuceneQuery.Parse("*wildcard AND title:test");
81+
82+
var options = new QueryValidationOptions
83+
{
84+
AllowLeadingWildcards = false
85+
};
86+
options.AllowedFields.Add("title");
87+
options.AllowedFields.Add("status");
88+
89+
var validationResult = await QueryValidator.ValidateAsync(result.Document, options);
90+
91+
if (!validationResult.IsValid)
92+
Console.WriteLine(validationResult.Message);
93+
```
94+
95+
### Entity Framework Integration
96+
97+
```csharp
98+
using Foundatio.LuceneQueryParser.EntityFramework;
99+
100+
var parser = new EntityFrameworkQueryParser();
101+
102+
// Build a filter expression from a Lucene query
103+
Expression<Func<Employee, bool>> filter = parser.BuildFilter<Employee>(
104+
"name:john AND salary:[50000 TO *] AND isActive:true"
105+
);
106+
107+
// Use with EF Core
108+
var results = await context.Employees.Where(filter).ToListAsync();
109+
```
110+
111+
## Supported Query Syntax
112+
113+
| Syntax | Example | Description |
114+
|--------|---------|-------------|
115+
| Terms | `hello`, `hello*`, `hel?o` | Simple terms with optional wildcards |
116+
| Phrases | `"hello world"`, `"hello world"~2` | Exact phrases with optional proximity |
117+
| Fields | `title:test`, `user.name:john` | Field-specific queries, supports nested paths |
118+
| Ranges | `price:[100 TO 500]`, `date:{* TO 2024-01-01}` | Inclusive `[]` or exclusive `{}` ranges |
119+
| Boolean | `AND`, `OR`, `NOT`, `+`, `-` | Boolean operators and prefix modifiers |
120+
| Groups | `(a OR b) AND c` | Parenthetical grouping |
121+
| Exists | `_exists_:field`, `_missing_:field` | Field existence checks |
122+
| Match All | `*:*` | Matches all documents |
123+
| Regex | `/pattern/` | Regular expression patterns |
124+
| Date Math | `now-1d`, `2024-01-01\|\|+1M/d` | Elasticsearch date math expressions |
125+
| Includes | `@include:savedQuery` | Reference saved/named queries |
126+
127+
## Creating Custom Visitors
128+
129+
Extend `QueryNodeVisitor` to create custom transformations:
130+
131+
```csharp
132+
using Foundatio.LuceneQueryParser.Ast;
133+
using Foundatio.LuceneQueryParser.Visitors;
134+
135+
public class LowercaseTermVisitor : QueryNodeVisitor
136+
{
137+
public override Task<QueryNode> VisitAsync(TermNode node, IQueryVisitorContext context)
138+
{
139+
node.Term = node.Term?.ToLowerInvariant();
140+
return Task.FromResult<QueryNode>(node);
141+
}
142+
143+
public override async Task<QueryNode> VisitAsync(FieldQueryNode node, IQueryVisitorContext context)
144+
{
145+
// Process this node's field
146+
node.Field = node.Field?.ToLowerInvariant();
147+
148+
// Visit children
149+
return await base.VisitAsync(node, context);
150+
}
151+
}
152+
153+
// Usage
154+
var visitor = new LowercaseTermVisitor();
155+
await visitor.RunAsync(result.Document);
156+
```
157+
158+
### Chaining Multiple Visitors
159+
160+
```csharp
161+
var chain = new ChainedQueryVisitor()
162+
.AddVisitor(new FieldAliasVisitor(aliases), priority: 10)
163+
.AddVisitor(new LowercaseTermVisitor(), priority: 20)
164+
.AddVisitor(new ValidationVisitor(), priority: 30);
165+
166+
await chain.AcceptAsync(document, context);
167+
```
168+
169+
## AST Node Types
170+
171+
| Node Type | Description |
172+
|-----------|-------------|
173+
| `QueryDocument` | Root node containing the parsed query |
174+
| `TermNode` | Simple term (e.g., `hello`) |
175+
| `PhraseNode` | Quoted phrase (e.g., `"hello world"`) |
176+
| `FieldQueryNode` | Field:value pair (e.g., `title:test`) |
177+
| `RangeNode` | Range query (e.g., `[1 TO 10]`) |
178+
| `BooleanQueryNode` | Boolean combination of clauses |
179+
| `GroupNode` | Parenthetical group |
180+
| `NotNode` | Negation wrapper |
181+
| `ExistsNode` | `_exists_:field` check |
182+
| `MissingNode` | `_missing_:field` check |
183+
| `MatchAllNode` | `*:*` match all |
184+
| `RegexNode` | Regular expression |
185+
| `MultiTermNode` | Multiple terms without explicit operators |
186+
187+
## Building
188+
189+
```bash
190+
dotnet build
191+
dotnet test
192+
```
193+
194+
## License
195+
196+
Apache 2.0

0 commit comments

Comments
 (0)