Skip to content

Semantic Search: First-Class Vector Similarity in a Multi-Dialect ORM

AI-powered search is everywhere, but if you use an ORM, you’ve probably hit this wall: the moment you need vector similarity, you’re forced to use raw SQL. Hand-written distance expressions, and dialect-specific quirks — all outside your type-safe query API.

UQL 0.3 changes that. Semantic search can now be used as a first-class citizen, all within a unified API.

You write
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:

PostgreSQL
SELECT "id", "title" FROM "Article"
ORDER BY "embedding" <=> $1::vector
LIMIT 10
MariaDB
SELECT `id`, `title` FROM `Article`
ORDER BY VEC_DISTANCE_COSINE(`embedding`, ?)
LIMIT 10
SQLite
SELECT `id`, `title` FROM `Article`
ORDER BY vec_distance_cosine(`embedding`, ?)
LIMIT 10

No raw SQL. No dialect checks. Same query everywhere.

For MongoDB, UQL translates into an Atlas $vectorSearch pipeline — same API, zero config:

MongoDB Atlas
[
{ "$vectorSearch": { "index": "embedding_index", "path": "embedding", "queryVector": ["..."], "numCandidates": 100, "limit": 10 } }
]

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 and SQLite just work — no extensions needed.

Project the computed distance into your results with $project — no duplicate computation:

import type { WithDistance } from 'uql-orm';
const results = await querier.findMany(Article, {
$select: { id: true, title: true },
$sort: { embedding: { $vector: queryVec, $distance: 'cosine', $project: 'similarity' } },
$limit: 10,
}) as WithDistance<Article, 'similarity'>[];
results[0].similarity; // autocomplete works ✓

WithDistance<E, K> adds a typed distance property to each result — your IDE autocompletes it, and typos are caught at compile time.

MetricPostgresMariaDBSQLiteMongoDB Atlas
cosine<=>✅ (index-defined)
l2<->✅ (index-defined)
inner<#>✅ (index-defined)
l1<+>
hamming<~>
TypeStorageUse Case
'vector'32-bit floatStandard embeddings (OpenAI, etc.)
'halfvec'16-bit float50% storage savings, near-identical accuracy
'sparsevec'SparseSPLADE, BM25-style sparse retrieval

halfvec and sparsevec are Postgres-only. MariaDB and SQLite transparently map them to their native VECTOR type — your entities work everywhere.

TypePostgresMariaDBMongoDB Atlas
HNSW✅ with m, efConstruction
IVFFlat✅ with lists
NativeVECTOR INDEX$vectorSearch

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,
});

No new concepts to learn. The query API you already know now handles AI search.

Terminal window
npm i uql-orm

We’d love to hear how you’re using vector search in your projects — join the Discord and let us know!