Skip to content

Semantic search: native vector similarity in a multi-dialect ORM

One $vector sort compiled to PostgreSQL, MariaDB, SQLite and MongoDB Atlas syntax side by side

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.

You write
const results = await pool.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::vector
LIMIT 10

The 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. The Atlas search index itself has to be created in Atlas; see the reference for details.

[
{
"$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({ type: Number }) id?: number;
@Field({ type: String }) 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.

Project the computed distance into your results with $project, without computing the distance twice:

import type { WithDistance } from 'uql-orm';
const results = (await pool.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.

Metric Postgres MariaDB SQLite MongoDB Atlas
cosine <=> ✅ (index-defined)
l2 <-> ✅ (index-defined)
inner <#> ✅ (index-defined)
l1 <+>
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.

Type Postgres MariaDB MongoDB Atlas
HNSW ✅ with m, efConstruction
IVFFlat ✅ with lists
Native VECTOR 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 pool.findMany(Article, {
$where: { category: 'science' },
$sort: {
embedding: { $vector: queryVec, $distance: 'cosine' },
title: 'asc',
},
$limit: 10,
});
Terminal window
npm i uql-orm

If you run into issues or missing features, open an issue on GitHub.



UQL is a JSON-native ORM for Node.js, Bun and Deno. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.