From dfc105e689a17eaf745a410b808bfc62a1999313 Mon Sep 17 00:00:00 2001 From: Lucas Z Date: Fri, 10 Apr 2026 13:08:46 +0200 Subject: [PATCH] docs: add fuzzy search and relevance ordering documentation Add documentation for the new `fuzzy` and `fuzzyContains` filter operators and `_relevance` ordering, available on PostgreSQL only. --- docs/orm/api/filter.md | 120 +++++++++++++++++++++++++++++++++++++++++ docs/orm/api/find.md | 2 + 2 files changed, 122 insertions(+) diff --git a/docs/orm/api/filter.md b/docs/orm/api/filter.md index d17b9f63..a8ff7602 100644 --- a/docs/orm/api/filter.md +++ b/docs/orm/api/filter.md @@ -19,6 +19,7 @@ You can filter on scalar fields with values or operators as supported by the fie - `in` `notIn`: all scalar fields - `contains` `startsWith` `endsWith`: `String` fields - `lt` `lte` `gt` `gte` `between`: `String`, `Int`, `BigInt`, `Float`, `Decimal`, and `Date` fields +- `fuzzy` `fuzzyContains`: `String` fields (PostgreSQL only, see [Fuzzy search filters](#fuzzy-search-filters)) A filter object can contain multiple field filters, and they are combined with `AND` semantic. You can also use the `AND`, `OR`, and `NOT` logical operators to combine filter objects to form a complex filter. @@ -118,6 +119,125 @@ Filters can be defined on conditions over relations. For one-to-one relations, y +## Fuzzy search filters + +:::info +Fuzzy search is only supported by the **PostgreSQL** provider and requires the `pg_trgm` and `unaccent` extensions to be enabled. +::: + +ZenStack provides built-in fuzzy search operators for `String` fields, powered by PostgreSQL's trigram similarity. This allows you to find records with approximate string matching - useful for handling typos, accent-insensitive searches, and substring matching. + +### Prerequisites + +Enable the required PostgreSQL extensions: + +```sql +CREATE EXTENSION IF NOT EXISTS unaccent; +CREATE EXTENSION IF NOT EXISTS pg_trgm; +``` + +### `fuzzy` - Trigram similarity + +The `fuzzy` operator matches strings that are similar to the search term, using the `%` trigram similarity operator with `unaccent` for accent-insensitive matching. + +```ts +// Find products with names similar to "creme" (matches "Crème brûlée", "Crème fraîche") +await db.product.findMany({ + where: { name: { fuzzy: 'creme' } } +}); + +// Handles typos: "Aple" matches "Apple" +await db.product.findMany({ + where: { name: { fuzzy: 'Aple' } } +}); +``` + +### `fuzzyContains` - Fuzzy substring matching + +The `fuzzyContains` operator checks if the search term is approximately contained within the field value, using the `<%` word similarity operator. + +```ts +// Find products where "choco" is approximately contained in the name +// Matches "Éclair au chocolat" +await db.product.findMany({ + where: { name: { fuzzyContains: 'choco' } } +}); +``` + +### Combining fuzzy with other filters + +Fuzzy operators can be combined with other filter operators on the same field or across fields: + +```ts +// Fuzzy + contains on the same field +await db.product.findMany({ + where: { name: { fuzzy: 'creme', contains: 'brûlée' } } +}); + +// Fuzzy on one field + standard filter on another +await db.product.findMany({ + where: { + name: { fuzzy: 'creme' }, + description: { contains: 'custard' } + } +}); +``` + +### Sorting by relevance + +Use `_relevance` in `orderBy` to sort results by how closely they match a search term, using PostgreSQL's `similarity()` function: + +```ts +await db.product.findMany({ + where: { name: { fuzzy: 'creme' } }, + orderBy: { + _relevance: { + fields: ['name'], + search: 'creme', + sort: 'desc' + } + } +}); + +// Relevance across multiple fields +await db.product.findMany({ + where: { + OR: [ + { name: { fuzzy: 'chocolate' } }, + { description: { fuzzy: 'chocolate' } }, + ] + }, + orderBy: { + _relevance: { + fields: ['name', 'description'], + search: 'chocolate', + sort: 'desc' + } + } +}); +``` + +:::caution +`_relevance` ordering cannot be combined with cursor-based pagination. +::: + +### Fuzzy filters in mutations + +Fuzzy operators work in all operations that accept `where` clauses, including `updateMany`, `deleteMany`, `count`, and `groupBy`: + +```ts +// Update all records matching the fuzzy search +await db.product.updateMany({ + where: { name: { fuzzy: 'creme' } }, + data: { category: 'French desserts' } +}); + +// Count fuzzy matches +const count = await db.product.count({ + where: { description: { fuzzyContains: 'pastry' } } +}); +``` + ## Query builder filters diff --git a/docs/orm/api/find.md b/docs/orm/api/find.md index 06f350fa..5eb47b8e 100644 --- a/docs/orm/api/find.md +++ b/docs/orm/api/find.md @@ -51,6 +51,8 @@ Use the `orderBy` field to control the sort field, direction, and null field pla +You can also sort by fuzzy search relevance using `_relevance`. See [Fuzzy search filters](./filter.md#sorting-by-relevance) for details. + ## Pagination You can use two strategies for pagination: offset-based or cursor-based. Pagination is not supported for `findUnique` and `findUniqueOrThrow`.