Semantic Search: Native Vector Similarity in a Multi-Dialect ORM
Most ORMs stop short of vector similarity. The moment you need it, you drop to raw SQL, hand-writing distance expressions and working around dialect quirks outside your type-safe query API.
UQL 0.3 adds native semantic search to the regular query API, including automatic index migration for HNSW and IVFFlat indexes.
What It Looks Like
Section titled “What It Looks Like”const results = await querier.findMany(Article, { $select: { id: true, title: true }, $sort: { embedding: { $vector: queryEmbedding, $distance: 'cosine' } }, $limit: 10,});UQL generates the right SQL for your database:
SELECT "id", "title" FROM "Article"ORDER BY "embedding" <=> $1::vectorLIMIT 10SELECT `id`, `title` FROM `Article`ORDER BY VEC_DISTANCE_COSINE(`embedding`, ?)LIMIT 10SELECT `id`, `title` FROM `Article`ORDER BY vec_distance_cosine(`embedding`, ?)LIMIT 10The same query works on every dialect, with no raw SQL or dialect checks in your code.
For MongoDB, UQL translates the same query into an Atlas $vectorSearch pipeline. Note that the Atlas search index itself must be created in Atlas; see the reference for details.
[ { "$vectorSearch": { "index": "embedding_index", "path": "embedding", "queryVector": ["..."], "numCandidates": 100, "limit": 10 } }]Entity Setup
Section titled “Entity Setup”Define your vector field and index; UQL handles schema generation, extension creation, and index building:
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({ type: 'vector', dimensions: 1536 }) embedding?: number[];}For Postgres, UQL automatically emits CREATE EXTENSION IF NOT EXISTS vector. MariaDB has vector support built in; SQLite requires loading the sqlite-vec extension.
Key Features
Section titled “Key Features”Distance Projection
Section titled “Distance Projection”Project the computed distance into your results with $project, without computing the distance twice:
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[0].distance;Annotate the result with the exported WithDistance<Article, 'distance'> helper to type the projected distance field.
5 Distance Metrics
Section titled “5 Distance Metrics”| Metric | Postgres | MariaDB | SQLite | MongoDB Atlas |
|---|---|---|---|---|
cosine |
<=> |
✅ | ✅ | ✅ (index-defined) |
l2 |
<-> |
✅ | ✅ | ✅ (index-defined) |
inner |
<#> |
❌ | ❌ | ✅ (index-defined) |
l1 |
<+> |
❌ | ❌ | ❌ |
hamming |
<~> |
❌ | ✅ | ❌ |
3 Vector Types
Section titled “3 Vector Types”| Type | Storage | Use Case |
|---|---|---|
'vector' |
32-bit float | Standard embeddings (OpenAI, etc.) |
'halfvec' |
16-bit float | 50% storage savings, near-identical accuracy |
'sparsevec' |
Sparse | SPLADE, BM25-style sparse retrieval |
halfvec and sparsevec are Postgres-only. MariaDB and SQLite transparently map them to their native VECTOR type, so your entities work everywhere.
Vector Indexes
Section titled “Vector Indexes”| Type | Postgres | MariaDB | MongoDB Atlas |
|---|---|---|---|
| HNSW | ✅ with m, efConstruction |
❌ | ❌ |
| IVFFlat | ✅ with lists |
❌ | ❌ |
| Native | ❌ | ✅ VECTOR INDEX |
✅ $vectorSearch |
Why $sort?
Section titled “Why $sort?”Vector similarity search is fundamentally sorting by distance. UQL reuses the existing $sort API, which composes naturally with $where, $select, $limit, and regular sort fields:
const results = await querier.findMany(Article, { $where: { category: 'science' }, $sort: { embedding: { $vector: queryVec, $distance: 'cosine' }, title: 'asc' }, $limit: 10,});Get Started
Section titled “Get Started”npm i uql-ormIf you run into issues or missing features, open an issue on GitHub.