Skip to content

Commit 0f6f175

Browse files
committed
feat!: rename bm25 APIs to paradedb and always use the paradedb access method (paradedb/paradedb#5706)
1 parent bd6941d commit 0f6f175

12 files changed

Lines changed: 92 additions & 80 deletions

File tree

paradedb-demo/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ Exercises:
1111
- `vectorColumn(3)` — native pgvector `vector(3)` column with `number[]``'[x,y,z]'` codec.
1212
- `paradeDbAll(keyCol)` + `paradeDbL2Distance(vecCol, query)` — vector Top-K (`@@@ pdb.all()` predicate, `<->` ORDER BY, LIMIT).
1313
- `CREATE EXTENSION pg_search` / `vector` via the docker init scripts (`init/*.sql`).
14-
- Automatic `CREATE INDEX ... USING bm25 (...) WITH (key_field='...')` via upstream's index-type registry.
14+
- Automatic `CREATE INDEX ... USING paradedb (...) WITH (key_field='...')` via upstream's index-type registry.
1515

16-
The bm25 index covers the text columns only: released pg_search builds reject vector columns in bm25 indexes (needs branch `mvp/vector-search`, paradedb/paradedb#5685), so vector Top-K runs unaccelerated here. The gated integration test in `test/vector.integration.test.ts` exercises the vector-in-bm25 index (via `renderBm25IndexDdl`) and skips with a clear reason on released builds.
16+
Requires a pg_search 0.25.0+ server, which registers the `paradedb` index access method; the docker image pin may lag behind, in which case `pnpm db:init` fails until the image catches up.
17+
18+
The ParadeDB index covers the text columns only: vector columns in ParadeDB indexes also need pg_search 0.25.0+ (paradedb/paradedb#5685), so on older builds vector Top-K runs unaccelerated. The gated integration test in `test/vector.integration.test.ts` exercises the vector-in-index DDL (via `renderParadeDbIndexDdl`) and skips with a clear reason when the server lacks support.
1719

1820
## Run it
1921

@@ -35,7 +37,7 @@ pnpm start -- vector '0.1,0.9,0.1' 3
3537
pnpm test
3638
```
3739

38-
`pnpm db:init` produces the BM25 index directly from the `constraints.index([...], { type: 'bm25', options: { key_field: 'id' } })` declaration in `prisma/contract.ts`.
40+
`pnpm db:init` produces the ParadeDB index directly from the `constraints.index([...], { type: 'paradedb', options: { key_field: 'id' } })` declaration in `prisma/contract.ts`.
3941

4042
Teardown:
4143

paradedb-demo/prisma/contract.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ export const contract = defineContract(
2929
Item: Item.sql(({ cols, constraints }) => ({
3030
table: 'item',
3131
indexes: [
32-
// Listing `cols.embedding` here requires pg_search with vector-in-bm25
33-
// support (branch mvp/vector-search); released builds reject vector
34-
// columns in bm25 indexes, so the demo indexes text fields only and
35-
// vector Top-K falls back to an unaccelerated sort.
32+
// Listing `cols.embedding` here requires vector-in-index support
33+
// (pg_search 0.25.0+ has it, but the docker image may lag), so the
34+
// demo indexes text fields only and vector Top-K falls back to an
35+
// unaccelerated sort.
3636
constraints.index([cols.category, cols.description, cols.id, cols.rating], {
37-
type: 'bm25',
37+
type: 'paradedb',
3838
options: { key_field: 'id' },
3939
name: 'item_bm25_idx',
4040
}),

paradedb-demo/src/prisma/contract.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import type {
3232
} from '@prisma-next/contract/types';
3333

3434
export type StorageHash =
35-
StorageHashBase<'sha256:4de46a86a05ed989a56dc452cdb4906e7822c33bb28cd0c73803592ea30ca55b'>;
35+
StorageHashBase<'sha256:a9d70bb2a26470e9db304a036763fda7d1bc77158069e9de655e91f8126c705f'>;
3636
export type ExecutionHash = ExecutionHashBase<string>;
3737
export type ProfileHash =
3838
ProfileHashBase<'sha256:9c8aa3114e84ed3b7ea2bd57526d9c2e1bf7c5292be694e9d3801f566fda7ccb'>;
@@ -141,7 +141,7 @@ type ContractBase = Omit<
141141
{
142142
readonly columns: readonly ['category', 'description', 'id', 'rating'];
143143
readonly name: 'item_bm25_idx';
144-
readonly type: 'bm25';
144+
readonly type: 'paradedb';
145145
readonly options: { readonly key_field: 'id' };
146146
},
147147
];

paradedb-demo/src/prisma/contract.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
"options": {
131131
"key_field": "id"
132132
},
133-
"type": "bm25"
133+
"type": "paradedb"
134134
}
135135
],
136136
"primaryKey": {
@@ -146,7 +146,7 @@
146146
"kind": "postgres-schema"
147147
}
148148
},
149-
"storageHash": "sha256:4de46a86a05ed989a56dc452cdb4906e7822c33bb28cd0c73803592ea30ca55b"
149+
"storageHash": "sha256:a9d70bb2a26470e9db304a036763fda7d1bc77158069e9de655e91f8126c705f"
150150
},
151151
"capabilities": {
152152
"postgres": {

paradedb-demo/test/vector.integration.test.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'dotenv/config';
2-
import { renderBm25IndexDdl } from '@prisma-next/extension-paradedb/ddl';
2+
import { renderParadeDbIndexDdl } from '@prisma-next/extension-paradedb/ddl';
33
import pg from 'pg';
44
import { beforeAll, describe, expect, it } from 'vitest';
55
import { loadAppConfig } from '../src/app-config';
@@ -33,34 +33,42 @@ describe.skipIf(SKIP)('paradedb vector integration', () => {
3333
expect(rows[0]?.distance).toBeCloseTo(distanceL2(query, [0.2, 0.1, 0.85]), 6);
3434
});
3535

36-
// Vector columns inside bm25 indexes need pg_search from branch
37-
// `mvp/vector-search` (paradedb/paradedb#5685); released builds reject the
38-
// DDL, so this Top-K pushdown coverage skips against current releases.
39-
it('creates a bm25 index over the vector column when pg_search supports it', async (ctx) => {
36+
// Vector columns inside ParadeDB indexes require pg_search 0.25.0+
37+
// (paradedb/paradedb#5685); older builds reject the DDL, so this Top-K
38+
// pushdown coverage skips there. A scratch table is used because a relation
39+
// may only have one ParadeDB index and `item` already carries the
40+
// contract-declared one.
41+
it('creates a ParadeDB index over a vector column when pg_search supports it', async (ctx) => {
4042
const { databaseUrl } = loadAppConfig();
4143
const client = new pg.Client({ connectionString: databaseUrl });
4244
await client.connect();
43-
const ddl = renderBm25IndexDdl({
44-
name: 'item_vector_bm25_idx',
45-
table: 'item',
46-
schema: 'public',
47-
keyField: 'id',
48-
columns: ['id', { column: 'embedding', metric: 'l2' }],
49-
});
5045
try {
46+
await client.query(
47+
'CREATE TABLE public.vector_idx_probe (id int4 PRIMARY KEY, embedding vector(3) NOT NULL)',
48+
);
49+
await client.query(
50+
`INSERT INTO public.vector_idx_probe VALUES (1, '[1,0,0]'), (2, '[0.1,0.9,0.1]'), (3, '[0,0,1]')`,
51+
);
52+
const ddl = renderParadeDbIndexDdl({
53+
name: 'vector_idx_probe_idx',
54+
table: 'vector_idx_probe',
55+
schema: 'public',
56+
keyField: 'id',
57+
columns: ['id', { column: 'embedding', metric: 'l2' }],
58+
});
5159
try {
5260
await client.query(ddl);
5361
} catch (error) {
5462
ctx.skip(
55-
`pg_search build does not support vector columns in bm25 indexes (needs branch mvp/vector-search): ${String(error)}`,
63+
`pg_search build does not support vector columns in its indexes (requires pg_search 0.25.0+): ${String(error)}`,
5664
);
5765
}
5866
const result = await client.query(
59-
`SELECT id FROM public.item WHERE id @@@ pdb.all() ORDER BY embedding <-> '[0.1,0.9,0.1]' LIMIT 2`,
67+
`SELECT id FROM public.vector_idx_probe WHERE id @@@ pdb.all() ORDER BY embedding <-> '[0.1,0.9,0.1]' LIMIT 2`,
6068
);
6169
expect(result.rows[0]?.id).toBe(2);
6270
} finally {
63-
await client.query('DROP INDEX IF EXISTS public.item_vector_bm25_idx');
71+
await client.query('DROP TABLE IF EXISTS public.vector_idx_probe CASCADE');
6472
await client.end();
6573
}
6674
});

paradedb/README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ ParadeDB full-text and vector search extension pack for Prisma Next.
44

55
## Overview
66

7-
This extension pack registers a `'bm25'` index type with the SQL family's index-type registry, so contracts can author BM25 full-text search indexes via the standard `constraints.index(...)` surface and the Postgres adapter emits `CREATE INDEX ... USING bm25 WITH (...)` DDL.
7+
This extension pack registers a `'paradedb'` index type with the SQL family's index-type registry, so contracts can author BM25 full-text search indexes via the standard `constraints.index(...)` surface and the Postgres adapter emits `CREATE INDEX ... USING paradedb WITH (...)` DDL.
8+
9+
Requires pg_search 0.25.0+, which registers the `paradedb` index access method. This release renames the previously bm25-named API (`renderBm25IndexDdl`, the `'bm25'` index type, ...) to paradedb names; older servers and the old names are not supported.
810

911
The v1 surface covers the `key_field` storage parameter only. Per-field tokenizer and column configuration is deferred to expression-index support.
1012

1113
It also provides native pgvector `vector(n)` column support (no pgvector ORM library required): a `paradedb/vector@1` codec, a `vectorColumn(n)` column helper, the three pgvector distance operators as query operations, and the `@@@ pdb.all()` match-all predicate required for vector Top-K queries.
1214

1315
## Responsibilities
1416

15-
- **bm25 index registration**: declares a `'bm25'` entry via `defineIndexTypes()` carrying an arktype validator for the bm25 options shape
17+
- **Index registration**: declares a `'paradedb'` entry via `defineIndexTypes()` carrying an arktype validator for the options shape
1618
- **Vector columns**: `vectorColumn(dimensions)` maps to `vector(n)` DDL; the `paradedb/vector@1` codec serializes `number[]``'[1,2,3]'` in both directions
1719
- **Vector queries**: `paradeDbL2Distance` (`<->`), `paradeDbCosineDistance` (`<=>`), `paradeDbInnerProduct` (`<#>`), and `paradeDbAll` (`@@@ pdb.all()`)
18-
- **Vector index DDL**: `renderBm25IndexDdl(...)` renders `CREATE INDEX ... USING bm25` statements with per-column vector opclasses for raw migrations
20+
- **Vector index DDL**: `renderParadeDbIndexDdl(...)` renders `CREATE INDEX ... USING paradedb` statements with per-column vector opclasses for raw migrations
1921
- **Extension descriptor**: declares the `paradedb/bm25` and `paradedb/vector` capabilities for contract-level feature detection
2022
- **Pack ref export**: ships a pure `/pack` entrypoint for TypeScript contract authoring
2123

@@ -35,7 +37,7 @@ pnpm add @prisma-next/extension-paradedb
3537

3638
### Contract definition
3739

38-
Author bm25 indexes via the standard `constraints.index(...)` surface; the registered `'bm25'` entry narrows `options` per-`type`:
40+
Author ParadeDB indexes via the standard `constraints.index(...)` surface; the registered `'paradedb'` entry narrows `options` per-`type`:
3941

4042
```typescript
4143
import { int4Column, textColumn } from '@prisma-next/adapter-postgres/column-types';
@@ -58,8 +60,8 @@ export const contract = defineContract({
5860
table: 'items',
5961
indexes: [
6062
constraints.index([cols.body], {
61-
name: 'item_body_bm25_idx',
62-
type: 'bm25',
63+
name: 'item_body_search_idx',
64+
type: 'paradedb',
6365
options: { key_field: 'id' },
6466
}),
6567
],
@@ -74,7 +76,7 @@ ParadeDB BM25 indexes require a `key_field` — a unique column that identifies
7476

7577
## Vector search
7678

77-
ParadeDB indexes pgvector `vector` columns inside its own bm25 access method. This pack ships the minimal pgvector surface natively — do not install pgvector ORM packages alongside it. Vector-in-bm25 indexing requires a pg_search build from branch `mvp/vector-search` ([paradedb/paradedb#5685](https://github.com/paradedb/paradedb/issues/5685)); on released builds the queries below still run, but without Top-K index acceleration. The `vector` (pgvector) extension must be installed for the column type itself.
79+
ParadeDB indexes pgvector `vector` columns inside its own index access method. This pack ships the minimal pgvector surface natively — do not install pgvector ORM packages alongside it. Vector-in-index support requires pg_search 0.25.0+ ([paradedb/paradedb#5685](https://github.com/paradedb/paradedb/issues/5685)); on older builds the queries below still run, but without Top-K index acceleration. The `vector` (pgvector) extension must be installed for the column type itself.
7880

7981
### Vector column declaration
8082

@@ -94,18 +96,18 @@ The column emits `vector(3)` DDL and reads/writes `number[]` values.
9496

9597
### Index creation
9698

97-
Vector columns listed in a `constraints.index([...], { type: 'bm25', ... })` declaration get the bm25 AM's default opclass, `vector_l2_ops` (L2). The contract index surface cannot express per-column opclasses yet, so for the cosine or inner-product metric render the DDL for a raw migration:
99+
Vector columns listed in a `constraints.index([...], { type: 'paradedb', ... })` declaration get the AM's default opclass, `vector_l2_ops` (L2). The contract index surface cannot express per-column opclasses yet, so for the cosine or inner-product metric render the DDL for a raw migration:
98100

99101
```typescript
100-
import { renderBm25IndexDdl } from '@prisma-next/extension-paradedb/ddl';
102+
import { renderParadeDbIndexDdl } from '@prisma-next/extension-paradedb/ddl';
101103

102-
renderBm25IndexDdl({
104+
renderParadeDbIndexDdl({
103105
name: 'item_vector_idx',
104106
table: 'item',
105107
keyField: 'id',
106108
columns: ['id', 'description', { column: 'embedding', metric: 'cosine' }],
107109
});
108-
// CREATE INDEX "item_vector_idx" ON "item" USING bm25
110+
// CREATE INDEX "item_vector_idx" ON "item" USING paradedb
109111
// ("id", "description", "embedding" vector_cosine_ops) WITH (key_field = 'id')
110112
```
111113

@@ -145,7 +147,7 @@ The ORDER BY distance operator must match the index opclass metric; a mismatch d
145147
## Not yet implemented
146148

147149
- Per-column / per-expression tokenizer configuration (deferred to expression-index support)
148-
- Per-column vector opclasses in `constraints.index(...)` (use `renderBm25IndexDdl` meanwhile)
150+
- Per-column vector opclasses in `constraints.index(...)` (use `renderParadeDbIndexDdl` meanwhile)
149151
- `halfvec` / `sparsevec` column types
150152
- `CREATE EXTENSION pg_search` / `CREATE EXTENSION vector` via migration planner (removed with upstream's `databaseDependencies` hook; install the extensions before `db init`)
151153
- Aggregation and highlight functions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export type VectorMetric = 'l2' | 'cosine' | 'ip';
22

3-
// Opclasses registered by pg_search on the bm25 access method. Same names as
3+
// Opclasses registered by pg_search on the paradedb access method. Same names as
44
// pgvector's but distinct catalog objects; `vector_l2_ops` is the AM default.
55
export const VECTOR_OPCLASSES = {
66
l2: 'vector_l2_ops',
@@ -16,53 +16,53 @@ export const VECTOR_DISTANCE_OPERATORS = {
1616
ip: '<#>',
1717
} as const satisfies Record<VectorMetric, string>;
1818

19-
export interface Bm25VectorIndexColumn {
19+
export interface ParadeDbVectorIndexColumn {
2020
readonly column: string;
2121
readonly metric?: VectorMetric;
2222
}
2323

24-
export interface Bm25IndexDdlOptions {
24+
export interface ParadeDbIndexDdlOptions {
2525
readonly name: string;
2626
readonly table: string;
2727
readonly schema?: string;
2828
readonly keyField: string;
29-
readonly columns: ReadonlyArray<string | Bm25VectorIndexColumn>;
29+
readonly columns: ReadonlyArray<string | ParadeDbVectorIndexColumn>;
3030
}
3131

3232
function quoteIdentifier(identifier: string): string {
3333
if (identifier.length === 0) {
34-
throw new Error('renderBm25IndexDdl: identifier cannot be empty');
34+
throw new Error('renderParadeDbIndexDdl: identifier cannot be empty');
3535
}
3636
if (identifier.includes('\0')) {
37-
throw new Error('renderBm25IndexDdl: identifier cannot contain null bytes');
37+
throw new Error('renderParadeDbIndexDdl: identifier cannot contain null bytes');
3838
}
3939
return `"${identifier.replace(/"/g, '""')}"`;
4040
}
4141

4242
function escapeLiteral(value: string): string {
4343
if (value.includes('\0')) {
44-
throw new Error('renderBm25IndexDdl: literal cannot contain null bytes');
44+
throw new Error('renderParadeDbIndexDdl: literal cannot contain null bytes');
4545
}
4646
return value.replace(/'/g, "''");
4747
}
4848

4949
/**
50-
* Renders `CREATE INDEX ... USING bm25 (...) WITH (key_field = '...')` DDL,
50+
* Renders `CREATE INDEX ... USING paradedb (...) WITH (key_field = '...')` DDL,
5151
* including per-column vector opclasses that the contract `constraints.index`
5252
* path cannot express yet. Indexes declared through `constraints.index` get the
5353
* default `vector_l2_ops` opclass for vector columns; use this helper (in a raw
5454
* migration) when the index needs the cosine or inner-product metric.
5555
*/
56-
export function renderBm25IndexDdl(options: Bm25IndexDdlOptions): string {
56+
export function renderParadeDbIndexDdl(options: ParadeDbIndexDdlOptions): string {
5757
if (options.columns.length === 0) {
58-
throw new Error('renderBm25IndexDdl: at least one column is required');
58+
throw new Error('renderParadeDbIndexDdl: at least one column is required');
5959
}
6060
const columnNames = options.columns.map((entry) =>
6161
typeof entry === 'string' ? entry : entry.column,
6262
);
6363
if (!columnNames.includes(options.keyField)) {
6464
throw new Error(
65-
`renderBm25IndexDdl: key_field "${options.keyField}" must be listed in columns`,
65+
`renderParadeDbIndexDdl: key_field "${options.keyField}" must be listed in columns`,
6666
);
6767
}
6868
const columnList = options.columns
@@ -71,7 +71,7 @@ export function renderBm25IndexDdl(options: Bm25IndexDdlOptions): string {
7171
const opclass = VECTOR_OPCLASSES[entry.metric ?? 'l2'];
7272
if (opclass === undefined) {
7373
throw new Error(
74-
`renderBm25IndexDdl: unknown vector metric ${JSON.stringify(entry.metric)}`,
74+
`renderParadeDbIndexDdl: unknown vector metric ${JSON.stringify(entry.metric)}`,
7575
);
7676
}
7777
return `${quoteIdentifier(entry.column)} ${opclass}`;
@@ -81,5 +81,5 @@ export function renderBm25IndexDdl(options: Bm25IndexDdlOptions): string {
8181
options.schema === undefined
8282
? quoteIdentifier(options.table)
8383
: `${quoteIdentifier(options.schema)}.${quoteIdentifier(options.table)}`;
84-
return `CREATE INDEX ${quoteIdentifier(options.name)} ON ${qualifiedTable} USING bm25 (${columnList}) WITH (key_field = '${escapeLiteral(options.keyField)}')`;
84+
return `CREATE INDEX ${quoteIdentifier(options.name)} ON ${qualifiedTable} USING paradedb (${columnList}) WITH (key_field = '${escapeLiteral(options.keyField)}')`;
8585
}

paradedb/src/exports/ddl.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
export {
2-
type Bm25IndexDdlOptions,
3-
type Bm25VectorIndexColumn,
2+
type ParadeDbIndexDdlOptions,
3+
type ParadeDbVectorIndexColumn,
44
type VectorMetric,
5-
renderBm25IndexDdl,
5+
renderParadeDbIndexDdl,
66
VECTOR_DISTANCE_OPERATORS,
77
VECTOR_OPCLASSES,
8-
} from '../core/bm25-index-ddl';
8+
} from '../core/paradedb-index-ddl';
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export type { Bm25IndexOptions, IndexTypes } from '../types/index-types';
1+
export type { IndexTypes, ParadeDbIndexOptions } from '../types/index-types';
22
export { paradedbIndexTypes } from '../types/index-types';

paradedb/src/types/index-types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { defineIndexTypes } from '@prisma-next/sql-contract/index-types';
22
import { type } from 'arktype';
33

4-
export const paradedbIndexTypes = defineIndexTypes().add('bm25', {
4+
export const paradedbIndexTypes = defineIndexTypes().add('paradedb', {
55
options: type({
66
'+': 'reject',
77
key_field: 'string',
88
}),
99
});
1010

1111
export type IndexTypes = typeof paradedbIndexTypes.IndexTypes;
12-
export type Bm25IndexOptions = IndexTypes['bm25']['options'];
12+
export type ParadeDbIndexOptions = IndexTypes['paradedb']['options'];

0 commit comments

Comments
 (0)