You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 253a690
Browse filesBrowse the repository at this point in the historyBrowse files
docs(mcp): recommend quoted Lucene values for exact matches and bare terms for body text (#3548)
Refs #3538
## What
Only descriptions and the query guide change; Lucene processing is untouched.
- **Query tool `where` / `whereLanguage` descriptions** (`packages/api/src/mcp/tools/query/schemas.ts`):
- for an exact match, quote the value: `SpanKind:"Server"` compiles to `=`, so a sort-key column such as `ServiceName` can use the primary key (SQL `=` is still listed);
- for text in the log body, write the term or phrase without a field name. That uses the source's token/text index; `Body:term`, `Body LIKE` and `Body ILIKE` read the whole column, and `Body:"…"` is exact equality on the whole body.
- **Query guide prompt** (`packages/api/src/mcp/prompts/dashboards/content.ts`): the same two points in the SUBSTRING MATCHING section and in common mistake 15; the "Contains" example now uses `StatusMessage` instead of `Body LIKE '%…%'`.
- `prompts.test.ts` asserts both points are in the guide.
## Why
Agents read "for exact match, use SQL" and mostly kept writing `ServiceName:x`, which matches `x-jobs` too and skips the primary key (one hour of logs: 108.9M vs 1.65M rows read). For body text, the descriptions pointed to `LIKE`, which can't use the index (27.7 GiB vs 121 MiB for the same matches). Details in #3538.
What each recommended form compiles to on `main`:
| Lucene | SQL |
|---|---|
| `ServiceName:api` | `ServiceName ILIKE '%api%'` |
| `ServiceName:"api"` | `ServiceName = 'api'` |
| `timeout` | `hasAllTokens(lower(Body), lower('timeout'))` (with a text index) |
| `"connection refused"` | `hasAllTokens(lower(Body), lower('connection refused')) AND lower(Body) LIKE lower('%connection refused%')` |
| `Body:"connection refused"` | `Body = 'connection refused'` |
The optional hint ("`ServiceName:api` matched 2 values") is not part of this PR; happy to follow up if you want it.
## Tests
- `npx jest src/mcp --testPathIgnorePatterns '\.int\.test\.ts$'` in `packages/api`: 25 suites, 484 tests pass.
- `tsc --noEmit`, `eslint` (no new warnings) and `prettier` clean.
When Lucene substring matching is FINE: free-text columns like Body, StatusMessage where substring is the intent.
895
+
For text in the log body, write a bare Lucene term or phrase (timeout, "connection refused"). It searches the
896
+
source's default search column (the body for logs) and uses its text index when the source has one. With an
897
+
index it matches whole tokens (timeout does not match ConnectionTimeoutException); for a substring inside a word, use
898
+
SQL ILIKE. Body:timeout, Body LIKE and Body ILIKE read the whole column, and Body:"..." is exact equality on
899
+
the whole body.
900
+
901
+
When Lucene substring matching is FINE: free-text columns like StatusMessage where substring is the intent.
893
902
When Lucene substring matching is WRONG: enum-like columns (SpanKind, SeverityText, StatusCode, ServiceName) where you want exact or true prefix match.
894
903
895
904
*** MAP-ATTRIBUTE GOTCHA ***
@@ -923,7 +932,7 @@ Alternative to Lucene. Set whereLanguage: "sql" when using SQL syntax.
923
932
Basic: SeverityText = 'error'
924
933
AND/OR: ServiceName = 'api' AND StatusCode = 'STATUS_CODE_ERROR'
Correct: where: 'SpanKind:"Server"' (whereLanguage: "lucene"; a quoted value is an exact match)
1565
1575
Correct: where: "ServiceName LIKE 'api%'" (whereLanguage: "sql"; true prefix match)
1566
-
Lucene field:value is a substring match (ilike with surrounding wildcards), not exact equality. Lucene field:value* is prefix-within-substring, not SQL LIKE 'value%'. Fine for free-text columns (Body, StatusMessage); wrong for enum-like columns where exact match matters (SpanKind, SeverityText, StatusCode) and wrong when true prefix matching is needed. Use SQL for exact match (=), prefix (LIKE 'x%'), contains (LIKE '%x%'), or regex (match(col, 'pattern')).
1576
+
Lucene field:value is a substring match (ilike with surrounding wildcards), not exact equality. Lucene field:value* is prefix-within-substring, not SQL LIKE 'value%'. Fine for free-text columns (StatusMessage; for Body use a bare term or phrase, which uses the index); wrong for enum-like columns where exact match matters (SpanKind, SeverityText, StatusCode) and wrong when true prefix matching is needed. Use SQL for exact match (=), prefix (LIKE 'x%'), contains (LIKE '%x%'), or regex (match(col, 'pattern')).
1567
1577
1568
1578
16. Forgetting that map-attribute values are often empty strings
1569
1579
Wrong: groupBy on SpanAttributes['db.collection.name'] without filtering empty values; the result has an empty-string bucket alongside the real ones, which renders as a blank row in tables and an unlabelled slice in pies.
0 commit comments