For AI agents: this page is also available as Markdown at /docs/sql.md. Every docs page is: add .md to its URL. llms.txt lists them all.

SQL and PostgreSQL

You can query a collection with a single SQL SELECT. The FROM clause names the collection, WHERE filters and searches, ORDER BY sorts, and GROUP BY turns into facets. Two dialects are accepted:

language Dialect Adds
sql MySQL MATCH ... AGAINST and related full-text functions
pgsql PostgreSQL tsvector / tsquery full-text search, pgvector distance operators, casts, date_trunc
curl -X POST https://api.warpsearch.io/v1/search \
  -H "Authorization: Bearer $WARPSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  --data @- <<'EOF'
{"language": "pgsql",
 "query": "SELECT title, year FROM books WHERE to_tsvector(title) @@ websearch_to_tsquery('dune -messiah') ORDER BY year DESC LIMIT 10"}
EOF

SQL is accepted wherever a search is: POST /v1/search, POST /v1/collections/{c}/search, GET /v1/collections/{c}/search?language=pgsql&q=..., POST /v1/collections/{c}/count, /v1/translate, and the MCP search, count and translate_query tools. When the request also names a collection, it must match FROM.

There is no PostgreSQL or MySQL wire protocol. psql, JDBC and ODBC drivers, BI tools and Grafana's PostgreSQL data source cannot connect. Send the query over HTTP or MCP.

What a query returns

The response is the same as for any other search: hits, facets, metrics and total.

SELECT category, COUNT(*), AVG(price) AS avg_price FROM products
WHERE MATCH(title) AGAINST('running')
GROUP BY category
{"total": 3, "hits": [],
 "facets": {"category": {"type": "terms", "field": "category", "buckets": [
   {"value": "shoes", "count": 2, "metrics": {"avg_price": 104.75}},
   {"value": "apparel", "count": 1, "metrics": {"avg_price": 12}}]}}}

Both dialects

Feature Notes
=, <> / !=, <, <=, >, >= On a text field = is a phrase match. On string, number and date fields it is exact.
IN, NOT IN, BETWEEN, IS [NOT] NULL, IS [NOT] TRUE / FALSE, AND, OR, NOT A bare boolean column means col = true. col = NULL matches nothing, as in SQL.
LIKE, NOT LIKE, ILIKE, with ESCAPE 'abc%' is a prefix query. Other patterns are wildcard queries (% and _), which depend on your plan. ILIKE matches case-sensitively, with a warning; to match a string field case-insensitively, give it a lowercase normalizer.
COUNT(*), COUNT(col), AVG, SUM, MIN, MAX
GROUP BY a, b Nested terms facets, 10 groups per level by default. LIMIT sets the number of groups at the first level.
HAVING COUNT(*) >= n Only COUNT(*) with > or >=.
ORDER BY Columns, aliases and _score. With GROUP BY, an aggregate or its alias orders the groups. The default is by count, descending.
SELECT DISTINCT col One column, returned as a terms facet.
LIMIT n OFFSET m OFFSET cannot be combined with GROUP BY.

Regular expressions and wildcard patterns depend on your plan and answer 403 forbidden when it does not include them. See Limits and errors.

PostgreSQL (pgsql)

SELECT title, year FROM books
WHERE to_tsvector(title) @@ to_tsquery('dune & !messiah')
  AND author ILIKE 'frank%' AND year BETWEEN 1960 AND 1980
  AND genre = ANY(ARRAY['scifi', 'fantasy'])
ORDER BY ts_rank(title, to_tsquery('dune')) DESC LIMIT 10 OFFSET 20
PostgreSQL What it does
to_tsvector(col) @@ to_tsquery('a & b | !c') & requires a term, | makes it optional, ! excludes it. Parentheses group. term:* is a prefix and a <-> b a phrase.
plainto_tsquery('a b') All words must match.
phraseto_tsquery('a b') The words as a phrase.
websearch_to_tsquery('"a b" -c or d') Quoted phrases, - to exclude, or between alternatives.
col @@ 'query', 'query'::tsquery Same as to_tsquery on that column.
to_tsvector(a || ' ' || b) Search several fields at once. coalesce(...) around a column is accepted.
ts_rank(...), ts_rank_cd(...) The relevance score, which is BM25. In ORDER BY it sorts by score.

Each field is searched with its own analyzer. A text search configuration such as 'english' is ignored with a warning, and so are weights (setweight, :A). Inside a <-> phrase only plain terms are allowed, and <N> distances other than <-> are not supported.

Vector search with pgvector operators

ORDER BY a distance and LIMIT k run a nearest-neighbor search on a vector field:

SELECT id, title FROM docs
WHERE lang = 'en' AND embedding <=> '[0.12, -0.03, 0.44]' < 0.4
ORDER BY embedding <=> '[0.12, -0.03, 0.44]'
LIMIT 10

The distance must be the only ORDER BY key, ascending. It cannot be combined with GROUP BY or aggregates, and a distance cannot be used in WHERE without the matching ORDER BY. See Vector search for indexes, recall and filters.

Other PostgreSQL features

Feature Notes
~, ~*, !~, !~* Regular expressions. ~* matches case-sensitively, with a warning.
= ANY(ARRAY[...]), = ANY('{a,b}'), <> ALL(...) Like IN and NOT IN.
BETWEEN SYMMETRIC, ISNULL, NOTNULL, IS UNKNOWN
'2024-01-01'::date, CAST(x AS int) Casts convert literals. A cast on a column is ignored with a warning.
now(), current_timestamp, current_date, date_trunc('day', now())
now() - interval '7 days', '7 days'::interval One unit per interval. '1 day 2 hours' is not supported; write '26 hours'.
GROUP BY date_trunc('day', ts) A date histogram. Units: second, minute, hour, day, week, month, quarter, year. Only as the first GROUP BY key, and LIMIT does not apply to it.
COUNT(DISTINCT col) Approximate (HyperLogLog, exact below about 3,000 values), with a warning.
FETCH FIRST n ROWS ONLY, LIMIT ALL, NULLS LAST NULLS FIRST is ignored with a warning; missing values sort last.
"Quoted" identifiers, table aliases, ORDER BY 1 Unquoted identifiers are lowercased, as in PostgreSQL.

MySQL (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
Feature Notes
MATCH(col, ...) AGAINST('words') Matches the words in any of the columns.
MATCH(...) AGAINST('...' IN BOOLEAN MODE) +, - and quotes, like a simple query string.
match_query(col, 'words'), match_phrase(col, 'exact words')
query_string('title:dune AND year:>1960') Lucene syntax over the default fields. Also simple_query_string.
score() The relevance score, in SELECT or ORDER BY.
REGEXP, NOT REGEXP Regular expressions.
<=> Null-safe equals. col <=> NULL matches documents without the field.
NOW(), CURDATE(), NOW() - INTERVAL 7 DAY, DATE_SUB(...) Date math. ISO-8601 strings compare as dates.

COUNT(DISTINCT ...) is not supported in sql; use pgsql for an approximate count.

Not supported

These return 400:

The same query in other languages, and how to see what WarpSearch made of yours, are on Query languages.

View this page as Markdown

Cookie settings

Optional tracking stays off until you choose it. Your choice applies to this browser for 180 days. Read the details.

Essential storage keeps you signed in, protects forms, and remembers this choice. It is always available.