# Query languages

Every search names a `language`, and WarpSearch translates it into one native query that runs on
the same engine. Use whichever language your team, your tools or your existing queries already
speak. You can run any of them through the REST API, and the Elasticsearch, OpenSearch and Solr
languages are also served by [drop-in endpoints](#compatible-endpoints) for existing clients.

| `language` | What you send | Typical use |
|---|---|---|
| `sql` | One `SELECT` over one collection | Analysts, BI, quick questions |
| `lucene` | Lucene `query_string` syntax | Search boxes, Kibana and Grafana habits |
| `es` | An Elasticsearch or OpenSearch `_search` body | Migrations, rich queries, aggregations, kNN |
| `solr` | Solr request parameters or a JSON Request body | Solr migrations |
| `logql` | A Loki LogQL query | Log search and log metrics |
| `grafana` | A Grafana Elasticsearch data source target | Dashboards |
| `luxir` | WarpSearch's native expression syntax | Application-authored queries, with safe variables |

The value `luxir` is the native language's name in the API. Filters, facets, metrics, sorting and
paging in a request work the same way whatever the language; see [Searching](searching.md) and
[Facets and aggregations](facets-and-aggregations.md).

## Sending a query

```bash
curl -X POST https://api.warpsearch.io/v1/search \
  -H "Authorization: Bearer $WARPSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"language": "sql", "query": "SELECT title, year FROM books WHERE year > 1960 ORDER BY year DESC LIMIT 5"}'
```

`POST /v1/search` takes the collection from the body's `collection` field or, for SQL, from `FROM`.
`POST /v1/collections/{c}/search` puts it in the path. The query is a string, except for `es` and
`grafana`, where it is a JSON object.

### See the translation

`POST /v1/translate` takes the same body and returns the native form without running it. The
response includes the native query, any `warnings`, and a `replay`: a request in the native
language that you can send to `/v1/search` to skip translation. Adding `"include_translation": true`
to a normal search puts the same information in its response.

This is the quickest way to learn what an unfamiliar language did with your query, and to check
for approximations before you tune relevance.

### Rules that apply to every language

- Fields are the collection's fields, as listed by `GET /v1/collections/{c}`. `text` fields are
  analyzed, `keyword` (string) fields match whole values.
- A query is translated exactly when the engine can express it. When only the scores would
  differ, you get the closest form and a warning. Otherwise the request fails with `400
  unsupported_feature`. Nothing is silently ignored.
- Warnings are in the response's `warnings` array, and on compatible endpoints in a `Warning: 299`
  header.
- Regex and wildcard queries depend on your plan and answer `403 forbidden` when it does not
  include them. See [Limits and errors](limits-and-errors.md).

## SQL

One `SELECT` over one collection.

```sql
SELECT title, year FROM books
WHERE year BETWEEN 1960 AND 1980 AND genre IN ('scifi', 'fantasy')
  AND MATCH(title) AGAINST('dune') AND author LIKE 'Frank%'
ORDER BY year DESC LIMIT 10 OFFSET 20
```

| Supported | Notes |
|---|---|
| `=`, `!=`, `<`, `<=`, `>`, `>=`, `IN`, `BETWEEN`, `LIKE`, `REGEXP`, `IS [NOT] NULL`, `AND`, `OR`, `NOT` | On a `text` field `=` is a phrase match, on a `keyword` field it is exact. |
| Full text: `MATCH(col) AGAINST('words')`, `match_query(col, 'words')`, `match_phrase(col, 'exact words')`, `query_string('lucene syntax')` | |
| Dates: ISO-8601 strings, `NOW() - INTERVAL 7 DAY` | |
| `COUNT(*)`, `COUNT(col)`, `AVG`, `SUM`, `MIN`, `MAX` | |
| `GROUP BY a, b`, `HAVING COUNT(*) >= n`, `ORDER BY <aggregate>`, `SELECT DISTINCT col` | Becomes nested terms facets. `LIMIT` caps the number of groups. `HAVING` supports `COUNT(*)` with `>` and `>=` only. |

> **Not supported:** joins, subqueries, `UNION`, window functions.

## Lucene

Lucene `query_string` syntax, as typed into Kibana or Grafana. Bare terms search the collection's
default fields.

```json
{"language": "lucene",
 "query": "title:dune AND year:[1960 TO 1980] -status:draft author:herb* name:smith~1 \"exact phrase\"~2 _exists_:isbn price:>=10"}
```

`?` and `*` become wildcards, a lone trailing `*` becomes a prefix query, and `/re/` is a regular
expression. Use `GET /v1/collections/{c}/search?q=...` for a one-line version.

## Elasticsearch and OpenSearch Query DSL

Send an `_search` body as the query object.

```json
{"language": "es",
 "query": {"query": {"bool": {"must": [{"match": {"title": "dune"}}],
                              "filter": [{"range": {"year": {"gte": 1960}}}]}},
           "aggs": {"genres": {"terms": {"field": "genre", "size": 5}}},
           "size": 10}}
```

| Area | Supported |
|---|---|
| Full text | `match`, `match_phrase`, `match_phrase_prefix` (one word), `match_bool_prefix`, `multi_match`, `combined_fields`, `query_string`, `simple_query_string` |
| Term-level | `term`, `terms`, `ids`, `exists`, `range`, `prefix`, `wildcard`, `regexp`, `fuzzy` |
| Compound | `bool`, `constant_score`, `dis_max`, `boosting`, `function_score` (weight, field value factor, decays, random score), `post_filter` |
| Geo | `geo_distance`, `geo_bounding_box`, `_geo_distance` sort |
| Vectors | Top-level `knn`, the `knn` query and the OpenSearch `knn` query, alone or combined with text for hybrid search. `num_candidates` is capped at 10000. See [Vector search](vector-search.md) and [Hybrid search](hybrid-search.md). |
| Other | `sort`, `from`/`size`, `_source`, `track_total_hits`, `min_score`, `collapse` with `inner_hits` |
| Aggregations | `terms`, `histogram`, `date_histogram`, `range`, `date_range`, `filters`, `filter`, `global`, `top_hits`, `avg`, `sum`, `min`, `max`, `value_count`, `stats`, `extended_stats`, `cardinality`, `percentiles`, `percentile_ranks` |

Some scores are approximations: `best_fields` and `dis_max` sum their clauses instead of taking the
maximum plus a tie breaker, and `cross_fields` does not blend terms. The same documents match, and
you get a warning.

> **Not supported:** `nested`, `has_child` and `has_parent`, `more_like_this`, `percolate`, scripts,
> `search_after`, `scroll`, point in time, `suggest`, `rescore` and `runtime_mappings`. These answer
> `400 unsupported_feature`. `highlight`, `explain` and `timeout` are accepted with a warning and
> have no effect.

> **Coming soon:** highlighting and polygon geo queries.

Add `?format=es` to `/v1/collections/{c}/search` for an Elasticsearch-shaped response, or use the
[compatible endpoints](#compatible-endpoints) with an existing client.

## Solr

Solr request parameters as a query string, or a JSON Request API object.

```json
{"language": "solr",
 "collection": "books",
 "query": "q=dune messiah&defType=edismax&qf=title^2 body&mm=2&fq={!tag=g}genre:scifi&facet=true&facet.field={!ex=g}genre"}
```

Supported: the standard, `dismax` and `edismax` parsers, local params and `$param` references, `fq`
with tags and exclusions, function queries and `bf`/`bq`, sorting (including `geodist()`),
`{!geofilt}` and `{!bbox}`, `{!knn}`, classic and JSON faceting (field, range, query, interval,
pivot), `stats`, and grouping by one field. Solr-shaped responses come from
`/compat/solr/{collection}/select`; see [Solr compatibility](compat-solr.md).

> **Not supported:** joins and block join, `collapse`, `cursorMark`, spellcheck, more-like-this,
> term statistics functions, atomic updates and delete by query.

## LogQL

Grafana Loki's query language. Labels are fields, and line filters search the collection's message
field.

```json
{"language": "logql",
 "collection": "logs",
 "query": "sum by (host) (count_over_time({app=\"api\"} |= \"timeout\" [5m]))",
 "time_range": {"from": "2026-10-20T00:00:00Z", "to": "2026-10-20T06:00:00Z"},
 "step": "1m"}
```

Metric queries come back as a date histogram, one series per group. Set the window with
`time_range` and the bucket width with `step`.

| Supported | Notes |
|---|---|
| Stream selectors on labels, such as `{app="api", level=~"warn\|error"}` | |
| Line filters `\|=`, `!=`, `\|~` | Match words and phrases in the message field, not raw substrings. |
| `json` and `logfmt` stages | No-ops, because fields are already indexed. |
| `count_over_time`, `rate`, `sum by`, `avg_over_time` with `unwrap` | `rate` returns counts per bucket. |

> **Not supported:** `without()`, `bytes_*` functions and binary operations between metric queries.

The Loki push and query HTTP APIs themselves are not offered; see
[Loki and LogQL](compat-loki-logql.md).

## Grafana

The Grafana Elasticsearch data source target model: `query`, `metrics` and `bucketAggs`, usually
with `time_range`. A `date_histogram` with `interval: auto` picks a round width for about 100
buckets.

## Native syntax

The native language is for queries your application writes. It has real `AND`/`OR`/`NOT`
precedence, and `$name` variables that are always values and never syntax, so user input cannot
change the shape of a query.

```json
{"language": "luxir",
 "query": "title:$q AND year:>=1960 AND status:=live",
 "vars": {"q": "dune messiah"},
 "limit": 10}
```

| Form | Meaning |
|---|---|
| `title:dune` | Word match, analyzed like the field. |
| `title:"dune messiah"~2` | Phrase, with slop. |
| `status:=live`, `status:=(a, b)` | Exact whole value, one or any of several. |
| `year:[1960 TO 1970]`, `year:>=1965` | Range. `{ }` excludes an endpoint, `*` leaves it open. |
| `created:>=NOW/DAY-30DAYS` | Date math. |
| `title:mess*`, `author:Herbet~1`, `year:*` | Prefix, fuzzy (up to 2 edits), field exists. |
| `+title:dune -tag:beta`, `title:dune^2` | Required, excluded, boost. |
| `wildcard(du*, field=title)`, `regex(dune\|kings, field=title)` | Wildcard and regular expression, whole term. |
| `simple_query($input, fields=[title, body])` | Forgiving search-box syntax that never fails to parse. |

There is no default field: name one. `?` and mid-word `*` are ordinary characters, and the pattern
queries above are functions.

## Compatible endpoints

Keep your client and point it at WarpSearch with the same API key:

| Client | Base URL |
|---|---|
| Elasticsearch | `https://api.warpsearch.io/compat/es` |
| OpenSearch | `https://api.warpsearch.io/compat/opensearch` |
| Solr | `https://api.warpsearch.io/compat/solr/{collection}` |

See [Elasticsearch compatibility](compat-elasticsearch.md), [OpenSearch compatibility](compat-opensearch.md)
and [Solr compatibility](compat-solr.md). Agents can run all of the languages above through the MCP
`search` and `translate_query` tools; see [Guide for AI agents](agents.md).
