Skip to content

Semantic Search

UQL supports vector similarity search natively, on PostgreSQL (pgvector), CockroachDB, MariaDB, SQLite (sqlite-vec), and MongoDB Atlas ($vectorSearch). The same $vector query works on all of them. This page is the operator reference; for an end-to-end walkthrough (ingestion, fullstack usage, RAG thresholds), see AI & RAG.

Define a vector field with type: 'vector' and dimensions. Optionally, add a vector index for efficient approximate nearest-neighbor (ANN) search.

You write
import { Entity, Id, Field, Index } from 'uql-orm';
@Entity()
@Index(['embedding'], { type: 'hnsw', distance: 'cosine', m: 16, efConstruction: 64 })
export class Article {
@Id() id?: number;
@Field() title?: string;
@Field() category?: string;
@Field({ type: 'vector', dimensions: 1536 })
embedding?: number[];
}

Use $sort on a vector field with $vector and an optional $distance metric:

You write
const results = await querier.findMany(Article, {
$select: { id: true, title: true },
$sort: { embedding: { $vector: queryEmbedding, $distance: 'cosine' } },
$limit: 10,
});
Generated SQL (PostgreSQL / CockroachDB)
SELECT "id", "title" FROM "Article"
ORDER BY "embedding" <=> $1::vector
LIMIT 10
Generated SQL (MariaDB)
SELECT `id`, `title` FROM `Article`
ORDER BY VEC_DISTANCE_COSINE(`embedding`, ?)
LIMIT 10
Generated SQL (SQLite)
SELECT `id`, `title` FROM `Article`
ORDER BY vec_distance_cosine(`embedding`, ?)
LIMIT 10

For MongoDB, UQL translates into a $vectorSearch aggregation pipeline:

Generated Pipeline (MongoDB Atlas)
[
{
"$vectorSearch": {
"index": "embedding_index",
"path": "embedding",
"queryVector": [/* queryEmbedding */],
"numCandidates": 100,
"limit": 10
}
}
]

Vector search composes naturally with $where and regular $sort fields:

You write
const results = await querier.findMany(Article, {
$where: { category: 'science' },
$sort: { embedding: { $vector: queryVec, $distance: 'cosine' }, title: 'asc' },
$limit: 10,
});
Generated SQL (PostgreSQL / CockroachDB)
SELECT * FROM "Article"
WHERE "category" = $1
ORDER BY "embedding" <=> $2::vector, "title" ASC
LIMIT 10
Generated SQL (MariaDB)
SELECT * FROM `Article`
WHERE `category` = ?
ORDER BY VEC_DISTANCE_COSINE(`embedding`, ?), `title` ASC
LIMIT 10
Generated SQL (SQLite)
SELECT * FROM `Article`
WHERE `category` = ?
ORDER BY vec_distance_cosine(`embedding`, ?), `title` ASC
LIMIT 10

For MongoDB, $where is merged into the $vectorSearch.filter for optimal pre-filtering, and secondary sorts become a separate $sort stage:

Generated Pipeline (MongoDB Atlas)
[
{
"$vectorSearch": {
"index": "embedding_index",
"path": "embedding",
"queryVector": [/* queryVec */],
"numCandidates": 100,
"limit": 10,
"filter": { "category": "science" }
}
},
{ "$sort": { "title": 1 } }
]

Metric Postgres Operator CockroachDB Operator MariaDB Function SQLite Function MongoDB Atlas Use Case
cosine <=> <=> VEC_DISTANCE_COSINE vec_distance_cosine ✅ (index-defined) Text embeddings (OpenAI, Cohere)
l2 <-> <-> VEC_DISTANCE_EUCLIDEAN vec_distance_L2 ✅ (index-defined) Image search, spatial data
inner <#> <#> ✅ (index-defined) Maximum inner product
l1 <+> Manhattan distance
hamming <~> vec_distance_hamming Binary embeddings

If omitted, $distance defaults to 'cosine'. You can also set a default per-field:

@Field({ type: 'vector', dimensions: 1536, distance: 'l2' })
embedding?: number[];

Queries on this field will use l2 unless overridden with $distance at query time.


Project the computed distance as a named field in the result with $project:

You write
import type { WithDistance } from 'uql-orm';
const results = (await querier.findMany(Article, {
$select: { id: true, title: true },
$sort: { embedding: { $vector: queryVec, $distance: 'cosine', $project: 'distance' } },
$limit: 10,
})) as WithDistance<Article, 'distance'>[];
results.forEach((r) => console.log(r.title, r.distance));
Generated SQL (PostgreSQL / CockroachDB)
SELECT "id", "title", "embedding" <=> $1::vector AS "distance" FROM "Article"
ORDER BY "distance"
LIMIT 10
Generated SQL (MariaDB)
SELECT `id`, `title`, VEC_DISTANCE_COSINE(`embedding`, ?) AS `distance` FROM `Article`
ORDER BY `distance`
LIMIT 10
Generated SQL (SQLite)
SELECT `id`, `title`, vec_distance_cosine(`embedding`, ?) AS `distance` FROM `Article`
ORDER BY `distance`
LIMIT 10

For MongoDB, $project adds a $meta: 'vectorSearchScore' projection:

Generated Pipeline (MongoDB Atlas)
[
{ "$vectorSearch": { "index": "embedding_index", "path": "embedding", "queryVector": ["..."], "numCandidates": 100, "limit": 10 } },
{ "$project": { "id": true, "title": true, "distance": { "$meta": "vectorSearchScore" } } }
]

UQL supports three vector storage types; use the one that best fits your model and performance needs:

Type SQL (Postgres) SQL (CockroachDB) Storage Max Dimensions Use Case
'vector' VECTOR(n) VECTOR(n) 32-bit float 2,000 Standard embeddings (OpenAI, etc.)
'halfvec' HALFVEC(n) VECTOR(n) 16-bit float 4,000 50% storage savings, near-identical accuracy
'sparsevec' SPARSEVEC(n) VECTOR(n) Sparse 1,000,000 SPLADE, BM25-style sparse retrieval
Examples
@Field({ type: 'vector', dimensions: 1536 }) // OpenAI ada-002
embedding?: number[];
@Field({ type: 'halfvec', dimensions: 1536 }) // Same model, half storage
embedding?: number[];
@Field({ type: 'sparsevec', dimensions: 30000 }) // SPLADE sparse
sparseEmbedding?: number[];

Define vector indexes with @Index() for efficient approximate nearest-neighbor (ANN) search:

Index Type Postgres CockroachDB MariaDB MongoDB Atlas Notes
hnsw USING hnsw with operator classes Best accuracy, higher memory
ivfflat USING ivfflat with lists param Faster build, large datasets
vector ✅ Native CREATE VECTOR INDEX ✅ Inline VECTOR INDEX CockroachDB’s and MariaDB’s own native vector index
vectorSearch ✅ Atlas vector search index MongoDB’s managed ANN index
Postgres HNSW
@Index(['embedding'], { type: 'hnsw', distance: 'cosine', m: 16, efConstruction: 64 })
Postgres IVFFlat
@Index(['embedding'], { type: 'ivfflat', distance: 'l2', lists: 100 })
CockroachDB
@Index(['embedding'], { type: 'vector', distance: 'cosine' })
MariaDB
@Index(['embedding'], { type: 'vector', distance: 'cosine', m: 8 })
MongoDB Atlas
@Index(['embedding'], { type: 'vectorSearch', name: 'my_search_index' })