---
url: /guide/troubleshooting.md
---
# Troubleshooting

This guide covers common issues and their solutions when using Foundatio.Parsers.

## Parse Errors

### Unexpected Character

**Error:** `Unexpected character at position X`

**Cause:** The query contains invalid syntax.

**Solution:**

```csharp
try
{
    var result = await parser.ParseAsync(query);
}
catch (FormatException ex)
{
    // Get position information
    var cursor = ex.Data["cursor"] as Cursor;
    if (cursor != null)
    {
        Console.WriteLine($"Error at line {cursor.Line}, column {cursor.Column}");
    }
    Console.WriteLine($"Message: {ex.Message}");
}
```

**Common causes:**

* Unbalanced parentheses: `(status:active`
* Invalid range syntax: `field:[1 TO]`
* Unescaped special characters: `url:https://example.com`

**Fix:** Escape special characters:

```csharp
// Escape colons in values
"url:https\\://example.com"

// Or use quotes
"url:\"https://example.com\""
```

### Empty Query

**Error:** Query returns no results or empty AST.

**Solution:** Check for empty or whitespace-only queries:

```csharp
if (string.IsNullOrWhiteSpace(query))
{
    // Handle empty query
    return new MatchAllQuery();
}

var result = await parser.ParseAsync(query);
```

## Validation Errors

### Field Not Allowed

**Error:** `Field 'X' is not allowed`

**Cause:** Field is not in the `AllowedFields` list.

**Solution:**

```csharp
var options = new QueryValidationOptions();
options.AllowedFields.Add("status");
options.AllowedFields.Add("name");
options.AllowedFields.Add("created");
// Add all fields users should be able to query
```

### Field Restricted

**Error:** `Field 'X' is restricted`

**Cause:** Field is in the `RestrictedFields` list.

**Solution:** Remove from restricted list or use a different field:

```csharp
var options = new QueryValidationOptions();
options.RestrictedFields.Remove("fieldname");
```

### Leading Wildcard Not Allowed

**Error:** `Leading wildcards are not allowed`

**Cause:** Query contains `*value` and `AllowLeadingWildcards` is false.

**Solution:**

```csharp
// Option 1: Allow leading wildcards
var options = new QueryValidationOptions {
    AllowLeadingWildcards = true
};

// Option 2: Rewrite query to use trailing wildcard
// Instead of: *smith
// Use: smith* (if appropriate for your use case)
```

### Query Too Deep

**Error:** `Query exceeds maximum depth of X`

**Cause:** Query nesting exceeds `AllowedMaxNodeDepth`.

**Solution:**

```csharp
var options = new QueryValidationOptions {
    AllowedMaxNodeDepth = 15  // Increase limit
};
```

### Unresolved Field

**Error:** `Field 'X' could not be resolved`

**Cause:** Field doesn't exist in Elasticsearch mapping and `AllowUnresolvedFields` is false.

**Solution:**

```csharp
// Option 1: Allow unresolved fields
var options = new QueryValidationOptions {
    AllowUnresolvedFields = true
};

// Option 2: Add field alias
var parser = new ElasticQueryParser(c => c
    .UseFieldMap(new Dictionary<string, string> {
        { "user_field", "actual.field.path" }
    }));

// Option 3: Force the next resolution to reload the server mapping.
parser.Configuration.MappingResolver.RefreshMapping();
```

## Elasticsearch Issues

### Query Returns No Results

**Possible causes:**

1. **Analyzed vs keyword field:**

```csharp
// Check if field is analyzed
var resolver = parser.Configuration.MappingResolver;
bool isAnalyzed = resolver.IsPropertyAnalyzed("title");

// For exact matches on analyzed fields, use .keyword
"title.keyword:\"Exact Title\""
```

2. **Case sensitivity:**

```csharp
// Elasticsearch is case-sensitive by default
// Use lowercase or configure analyzer
"status:Active"  // May not match "active"
```

3. **Filter vs query context:**

```csharp
// By default, queries are wrapped in filter (no scoring)
// For full-text search, enable scoring
var context = new ElasticQueryVisitorContext { UseScoring = true };
var query = await parser.BuildQueryAsync("search terms", context);
```

### Nested Query Not Working

**Cause:** Nested support not enabled or field not detected as nested.

**Solution:**

```csharp
var parser = new ElasticQueryParser(c => c
    .UseMappings(client, "my-index")
    .UseNested());  // Enable nested support

// Verify field is nested
var resolver = parser.Configuration.MappingResolver;
bool isNested = resolver.IsNestedPropertyType("comments");
```

For more details on how nested queries are traversed and built, see [Nested Queries and Visitor Traversal](./nested-queries).

### Geo Query Fails

**Cause:** Geo resolver not configured or returns invalid coordinates.

**Solution:**

```csharp
var parser = new ElasticQueryParser(c => c
    .UseMappings(client, "my-index")
    .UseGeo(location => {
        // Ensure valid coordinates are returned
        Console.WriteLine($"Resolving: {location}");
        var coords = ResolveLocation(location);
        Console.WriteLine($"Resolved to: {coords}");
        return coords;
    }));
```

### Aggregation Field Error

**Error:** `Fielddata is disabled on text fields by default`

**Cause:** Trying to aggregate on an analyzed text field.

**Solution:**

```csharp
// The parser should automatically use .keyword sub-field
// If not, check your mapping has keyword sub-field:
// "title": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }

// Or use field alias
var parser = new ElasticQueryParser(c => c
    .UseFieldMap(new Dictionary<string, string> {
        { "title", "title.keyword" }
    }));
```

## SQL/EF Core Issues

### Dynamic LINQ Parse Error

**Error:** `No property or field 'X' exists in type 'Y'`

**Cause:** Field name doesn't match entity property.

**Solution:**

```csharp
// Check available fields
var context = parser.GetContext(db.Products.EntityType);
foreach (var field in context.Fields)
{
    Console.WriteLine($"{field.FullName} ({field.GetType().Name})");
}

// Use correct casing (EF Core is case-sensitive)
"Status:active"  // Not "status:active"
```

### Navigation Property Error

**Error:** Query on navigation property fails.

**Solution:**

```csharp
// Increase navigation depth
var parser = new SqlQueryParser(c => c
    .SetFieldDepth(3));

// Check if navigation is included
var context = parser.GetContext(db.Products.EntityType);
var navFields = context.Fields.Where(f => f.IsNavigation);
```

### Full-Text Search Not Working

**Cause:** Full-text catalog not configured or fields not indexed.

**Solution:**

```csharp
// Ensure full-text catalog exists
// CREATE FULLTEXT CATALOG ftCatalog AS DEFAULT;

// Ensure full-text index exists on table
// CREATE FULLTEXT INDEX ON Products(Name, Description) KEY INDEX PK_Products;

// Configure parser
var parser = new SqlQueryParser(c => c
    .SetFullTextFields(new[] { "Name", "Description" }));
```

## Include Issues

### Include Not Expanding

**Cause:** Include name not found in resolver.

**Solution:**

```csharp
var parser = new ElasticQueryParser(c => c
    .UseIncludes(async name => {
        var include = await GetInclude(name);
        if (include == null)
        {
            Console.WriteLine($"Include not found: {name}");
        }
        return include;
    }));
```

### Recursive Include Detected

**Error:** `Recursive include detected: X`

**Cause:** Include references itself directly or indirectly.

**Solution:**

```csharp
// Check your include definitions for cycles
var includes = new Dictionary<string, string> {
    { "a", "@include:b" },
    { "b", "@include:a" }  // Cycle!
};

// Fix by removing the cycle
var includes = new Dictionary<string, string> {
    { "a", "@include:b" },
    { "b", "status:active" }  // No cycle
};
```

## Performance Issues

### Slow Query Parsing

**Cause:** Complex queries or many visitors.

**Solution:**

```csharp
// Reuse parser instance
private readonly ElasticQueryParser _parser;

public MyService()
{
    _parser = new ElasticQueryParser(c => c
        .UseMappings(client, "my-index"));
}

// Don't create new parser for each request
```

### Slow Mapping Resolution

**Cause:** Mapping fetched from Elasticsearch on every query.

**Solution:**

```csharp
// Resolved fields are cached. Manual refresh is only needed after a known change to an already resolved field:
parser.Configuration.MappingResolver.RefreshMapping();

// For production, create resolver once and share
var resolver = ElasticMappingResolver.Create(client, "my-index");
var parser1 = new ElasticQueryParser(c => c.UseMappings(resolver));
var parser2 = new ElasticQueryParser(c => c.UseMappings(resolver));
```

**Cause:** Queries continually reference fields that do not exist, making the resolver repeatedly eligible
to reload the mapping.

Fields that cannot be resolved reload the mapping on their own short interval
(`UnmappedFieldRefreshInterval`, default 5 seconds) because that is how dynamically created fields become
visible. Concurrent reload attempts are coalesced into a single server mapping fetch per resolver. Under
continuous misses, the default permits about 12 automatic attempts per minute for each resolver in each
process; cold starts and explicit `RefreshMapping()` calls are outside that ceiling. Raise the interval if a
workload legitimately queries many non-existent fields:

```csharp
resolver.UnmappedFieldRefreshInterval = TimeSpan.FromSeconds(30);
```

Reuse one resolver per concrete index and process. A resolver created for every request has its own cache,
throttle, and in-flight request, so it defeats this protection. Look for the
`Unable to resolve mapping for field {Field}` warning: it means a reload was suppressed by the throttle and
the field is being treated as unmapped.

### Recently Created Field Resolves As Unmapped

**Cause:** The field was created after the resolver loaded the index mapping — for example by a dynamic
template, which only adds the field to the mapping once the first document using it has been indexed.

**Symptoms:**

* A `nested` field is queried as a flat field, so the query succeeds but returns no results.
* Sorting fails with `Fielddata is disabled on [field] in [index]`, because the resolver could not find the
  `.keyword` / `.sort` sub-field and fell back to the analyzed field.

**Solution:** the resolver becomes eligible to reload after `UnmappedFieldRefreshInterval`. This is a cooldown
after a completed attempt, not a five-second correctness deadline. To pick up an application-controlled
change immediately, call `RefreshMapping()` after Elasticsearch acknowledges it:

```csharp
resolver.RefreshMapping();
```

If the field stays unmapped for much longer than `UnmappedFieldRefreshInterval`, look for the
`did not complete within` warning. It means a mapping reload was already running but took longer than
`MappingRefreshWaitTimeout` (default 30 seconds), so resolutions gave up waiting for it. Raise `MappingRefreshWaitTimeout` to
match how long your `GetMapping` call actually takes:

```csharp
resolver.MappingRefreshWaitTimeout = TimeSpan.FromMinutes(2);
```

The mapping callback must enforce a finite timeout and must not call the same resolver. The built-in client
factories use the Elasticsearch client's configured request timeout; keep it below
`MappingRefreshWaitTimeout` if joining resolutions must wait for the result. If treating an unresolved field
as unmapped could generate an incorrect query, configure validation with `AllowUnresolvedFields = false` so
the request fails instead.

## Debugging

### Enable Logging

```csharp
var parser = new ElasticQueryParser(c => c
    .SetLoggerFactory(loggerFactory));

// Or for LuceneQueryParser, use ILogger directly
var loggerFactory = LoggerFactory.Create(b => b.AddConsole());
```

### Inspect AST

```csharp
var parser = new LuceneQueryParser();
var result = await parser.ParseAsync(query);

// Print AST structure
string debug = DebugQueryVisitor.Run(result);
Console.WriteLine(debug);
```

### Inspect Generated Query

```csharp
// Regenerate query string from AST
string regenerated = GenerateQueryVisitor.Run(result);
Console.WriteLine($"Parsed as: {regenerated}");
```

### Inspect Elasticsearch Request

```csharp
var response = await client.SearchAsync<MyDoc>(s => s
    .Index("my-index")
    .Query(_ => query));

// Get the actual request sent
string request = response.GetRequest();
Console.WriteLine(request);

// Check for errors
if (!response.IsValidResponse)
{
    Console.WriteLine(response.GetErrorMessage());
}
```

## Getting Help

If you're still having issues:

1. **Check the tests** - The test files contain many examples:
   * `tests/Foundatio.Parsers.LuceneQueries.Tests/`
   * `tests/Foundatio.Parsers.ElasticQueries.Tests/`
   * `tests/Foundatio.Parsers.SqlQueries.Tests/`

2. **Enable detailed logging** - Use `LogLevel.Trace` for maximum detail

3. **Open an issue** - [GitHub Issues](https://github.com/FoundatioFx/Foundatio.Parsers/issues)

4. **Join Discord** - [Foundatio Discord](https://discord.gg/6HxgFCx)
