Skip to content

Commit db74263

Browse files
nipunn1313Convex, Inc.
authored andcommitted
Switch the staged index syntax to {staged:true} (#40020)
Add tests to ensure they don't show up in the table definitions. GitOrigin-RevId: 45088d58fc689e2ad0dad96bb7fcca53d62f64fe
1 parent 7b5386f commit db74263

File tree

2 files changed

+233
-55
lines changed

2 files changed

+233
-55
lines changed

npm-packages/convex/src/server/schema.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,96 @@ describe("DataModelFromSchemaDefinition", () => {
453453
};
454454
assert<Equals<DataModel, ExpectedDataModel>>();
455455
});
456+
457+
test("defineSchema creates staged indexes", () => {
458+
const schema = defineSchema({
459+
table: defineTable({
460+
enabled: v.string(),
461+
enabled2: v.string(),
462+
staged: v.string(),
463+
})
464+
.index("by_enabled", ["enabled"])
465+
.index("by_enabled2", ["enabled2"], { staged: false })
466+
.index("by_staged", ["staged"], { staged: true }),
467+
});
468+
type Indexes = DataModelFromSchemaDefinition<
469+
typeof schema
470+
>["table"]["indexes"];
471+
type ExpectedIndexes = {
472+
by_enabled: ["enabled", "_creationTime"];
473+
by_enabled2: ["enabled2", "_creationTime"];
474+
by_id: ["_id"];
475+
by_creation_time: ["_creationTime"];
476+
};
477+
assert<Equals<Indexes, ExpectedIndexes>>();
478+
});
479+
480+
test("defineSchema creates staged search indexes", () => {
481+
const schema = defineSchema({
482+
table: defineTable({
483+
enabled: v.string(),
484+
enabled2: v.string(),
485+
staged: v.string(),
486+
})
487+
.searchIndex("by_enabled", { searchField: "enabled" })
488+
.searchIndex("by_enabled2", { searchField: "enabled2", staged: false })
489+
.searchIndex("by_staged", { searchField: "staged", staged: true }),
490+
});
491+
type SearchIndexes = DataModelFromSchemaDefinition<
492+
typeof schema
493+
>["table"]["searchIndexes"];
494+
type ExpectedSearchIndexes = {
495+
by_enabled: {
496+
searchField: "enabled";
497+
filterFields: never;
498+
};
499+
by_enabled2: {
500+
searchField: "enabled2";
501+
filterFields: never;
502+
};
503+
};
504+
assert<Equals<SearchIndexes, ExpectedSearchIndexes>>();
505+
});
506+
507+
test("defineSchema creates staged vector indexes", () => {
508+
const schema = defineSchema({
509+
table: defineTable({
510+
enabled: v.string(),
511+
enabled2: v.string(),
512+
staged: v.string(),
513+
})
514+
.vectorIndex("by_enabled", {
515+
vectorField: "enabled",
516+
dimensions: 1536,
517+
})
518+
.vectorIndex("by_enabled2", {
519+
vectorField: "enabled2",
520+
dimensions: 1536,
521+
staged: false,
522+
})
523+
.vectorIndex("by_staged", {
524+
vectorField: "staged",
525+
dimensions: 1536,
526+
staged: true,
527+
}),
528+
});
529+
type VectorIndexes = DataModelFromSchemaDefinition<
530+
typeof schema
531+
>["table"]["vectorIndexes"];
532+
type ExpectedVectorIndexes = {
533+
by_enabled: {
534+
vectorField: "enabled";
535+
dimensions: number;
536+
filterFields: never;
537+
};
538+
by_enabled2: {
539+
vectorField: "enabled2";
540+
dimensions: number;
541+
filterFields: never;
542+
};
543+
};
544+
assert<Equals<VectorIndexes, ExpectedVectorIndexes>>();
545+
});
456546
});
457547

458548
test("defineSchema generates search index types", () => {

npm-packages/convex/src/server/schema.ts

Lines changed: 143 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,26 @@ export type SearchIndex = {
147147
searchField: string;
148148
filterFields: string[];
149149
};
150+
151+
/**
152+
* Options for defining an index.
153+
*
154+
* @public
155+
*/
156+
export interface IndexOptions {
157+
/**
158+
* Whether the index should be staged.
159+
*
160+
* For large tables, index backfill can be slow. Staging an index allows you
161+
* to push the schema and enable the index later.
162+
*
163+
* If `staged` is `true`, the index will be staged and will not be enabled
164+
* until the staged flag is removed. Staged indexes do not block push
165+
* completion. Staged indexes cannot be used in queries.
166+
*/
167+
staged?: boolean;
168+
}
169+
150170
/**
151171
* The definition of a table within a schema.
152172
*
@@ -210,10 +230,9 @@ export class TableDefinition<
210230
>(
211231
name: IndexName,
212232
fields: [FirstFieldPath, ...RestFieldPaths],
233+
options?: IndexOptions & { staged: false },
213234
): TableDefinition<
214235
DocumentType,
215-
// Update `Indexes` to include the new index and use `Expand` to make the
216-
// types look pretty in editors.
217236
Expand<
218237
Indexes &
219238
Record<
@@ -223,28 +242,49 @@ export class TableDefinition<
223242
>,
224243
SearchIndexes,
225244
VectorIndexes
226-
> {
227-
this.indexes.push({ indexDescriptor: name, fields });
228-
return this;
229-
}
245+
>;
230246

231247
/**
248+
* Define a staged index on this table.
232249
*
233-
* @internal
234-
* @param name - The name of the staged index.
250+
* For large tables, index backfill can be slow. Staging an index allows you
251+
* to push the schema and enable the index later.
252+
*
253+
* If `staged` is `true`, the index will be staged and will not be enabled
254+
* until the staged flag is removed. Staged indexes do not block push
255+
* completion. Staged indexes cannot be used in queries.
256+
*
257+
* To learn about indexes, see [Defining Indexes](https://docs.convex.dev/using/indexes).
258+
*
259+
* @param name - The name of the index.
235260
* @param fields - The fields to index, in order. Must specify at least one
236261
* field.
237-
* @returns A {@link TableDefinition} with this staged index included.
262+
* @returns A {@link TableDefinition} with this index included.
238263
*/
239-
stagedIndex<
264+
index<
265+
IndexName extends string,
266+
FirstFieldPath extends ExtractFieldPaths<DocumentType>,
267+
RestFieldPaths extends ExtractFieldPaths<DocumentType>[],
268+
>(
269+
name: IndexName,
270+
fields: [FirstFieldPath, ...RestFieldPaths],
271+
options: IndexOptions & { staged: true },
272+
): TableDefinition<DocumentType, Indexes, SearchIndexes, VectorIndexes>;
273+
274+
index<
240275
IndexName extends string,
241276
FirstFieldPath extends ExtractFieldPaths<DocumentType>,
242277
RestFieldPaths extends ExtractFieldPaths<DocumentType>[],
243278
>(
244279
name: IndexName,
245280
fields: [FirstFieldPath, ...RestFieldPaths],
246-
): TableDefinition<DocumentType, Indexes, SearchIndexes, VectorIndexes> {
247-
this.stagedDbIndexes.push({ indexDescriptor: name, fields });
281+
options?: IndexOptions,
282+
) {
283+
if (options?.staged) {
284+
this.stagedDbIndexes.push({ indexDescriptor: name, fields });
285+
} else {
286+
this.indexes.push({ indexDescriptor: name, fields });
287+
}
248288
return this;
249289
}
250290

@@ -263,7 +303,10 @@ export class TableDefinition<
263303
FilterFields extends ExtractFieldPaths<DocumentType> = never,
264304
>(
265305
name: IndexName,
266-
indexConfig: Expand<SearchIndexConfig<SearchField, FilterFields>>,
306+
indexConfig: Expand<
307+
SearchIndexConfig<SearchField, FilterFields> &
308+
IndexOptions & { staged?: false }
309+
>,
267310
): TableDefinition<
268311
DocumentType,
269312
Indexes,
@@ -280,38 +323,59 @@ export class TableDefinition<
280323
>
281324
>,
282325
VectorIndexes
283-
> {
284-
this.searchIndexes.push({
285-
indexDescriptor: name,
286-
searchField: indexConfig.searchField,
287-
filterFields: indexConfig.filterFields || [],
288-
});
289-
return this;
290-
}
326+
>;
291327

292328
/**
293-
* Stage a search index on this table.
329+
* Define a staged search index on this table.
294330
*
295-
* To learn about text search indexes, see [Search](https://docs.convex.dev/text-search).
331+
* For large tables, index backfill can be slow. Staging an index allows you
332+
* to push the schema and enable the index later.
333+
*
334+
* If `staged` is `true`, the index will be staged and will not be enabled
335+
* until the staged flag is removed. Staged indexes do not block push
336+
* completion. Staged indexes cannot be used in queries.
337+
*
338+
* To learn about search indexes, see [Search](https://docs.convex.dev/text-search).
296339
*
297-
* @internal
298340
* @param name - The name of the index.
299-
* @param indexConfig - The text index configuration object.
300-
* @returns A {@link TableDefinition} with this text index included.
341+
* @param indexConfig - The search index configuration object.
342+
* @returns A {@link TableDefinition} with this search index included.
301343
*/
302-
stagedSearchIndex<
344+
searchIndex<
303345
IndexName extends string,
304346
SearchField extends ExtractFieldPaths<DocumentType>,
305347
FilterFields extends ExtractFieldPaths<DocumentType> = never,
306348
>(
307349
name: IndexName,
308-
indexConfig: Expand<SearchIndexConfig<SearchField, FilterFields>>,
309-
): TableDefinition<DocumentType, Indexes, SearchIndexes, VectorIndexes> {
310-
this.stagedSearchIndexes.push({
311-
indexDescriptor: name,
312-
searchField: indexConfig.searchField,
313-
filterFields: indexConfig.filterFields || [],
314-
});
350+
indexConfig: Expand<
351+
SearchIndexConfig<SearchField, FilterFields> &
352+
IndexOptions & { staged: true }
353+
>,
354+
): TableDefinition<DocumentType, Indexes, SearchIndexes, VectorIndexes>;
355+
356+
searchIndex<
357+
IndexName extends string,
358+
SearchField extends ExtractFieldPaths<DocumentType>,
359+
FilterFields extends ExtractFieldPaths<DocumentType> = never,
360+
>(
361+
name: IndexName,
362+
indexConfig: Expand<
363+
SearchIndexConfig<SearchField, FilterFields> & IndexOptions
364+
>,
365+
) {
366+
if (indexConfig.staged) {
367+
this.stagedSearchIndexes.push({
368+
indexDescriptor: name,
369+
searchField: indexConfig.searchField,
370+
filterFields: indexConfig.filterFields || [],
371+
});
372+
} else {
373+
this.searchIndexes.push({
374+
indexDescriptor: name,
375+
searchField: indexConfig.searchField,
376+
filterFields: indexConfig.filterFields || [],
377+
});
378+
}
315379
return this;
316380
}
317381

@@ -330,7 +394,10 @@ export class TableDefinition<
330394
FilterFields extends ExtractFieldPaths<DocumentType> = never,
331395
>(
332396
name: IndexName,
333-
indexConfig: Expand<VectorIndexConfig<VectorField, FilterFields>>,
397+
indexConfig: Expand<
398+
VectorIndexConfig<VectorField, FilterFields> &
399+
IndexOptions & { staged?: false }
400+
>,
334401
): TableDefinition<
335402
DocumentType,
336403
Indexes,
@@ -346,40 +413,61 @@ export class TableDefinition<
346413
}
347414
>
348415
>
349-
> {
350-
this.vectorIndexes.push({
351-
indexDescriptor: name,
352-
vectorField: indexConfig.vectorField,
353-
dimensions: indexConfig.dimensions,
354-
filterFields: indexConfig.filterFields || [],
355-
});
356-
return this;
357-
}
416+
>;
358417

359418
/**
360-
* Stage a vector index on this table.
419+
* Define a staged vector index on this table.
420+
*
421+
* For large tables, index backfill can be slow. Staging an index allows you
422+
* to push the schema and enable the index later.
423+
*
424+
* If `staged` is `true`, the index will be staged and will not be enabled
425+
* until the staged flag is removed. Staged indexes do not block push
426+
* completion. Staged indexes cannot be used in queries.
361427
*
362428
* To learn about vector indexes, see [Vector Search](https://docs.convex.dev/vector-search).
363429
*
364-
* @internal
365430
* @param name - The name of the index.
366431
* @param indexConfig - The vector index configuration object.
367432
* @returns A {@link TableDefinition} with this vector index included.
368433
*/
369-
stagedVectorIndex<
434+
vectorIndex<
370435
IndexName extends string,
371436
VectorField extends ExtractFieldPaths<DocumentType>,
372437
FilterFields extends ExtractFieldPaths<DocumentType> = never,
373438
>(
374439
name: IndexName,
375-
indexConfig: Expand<VectorIndexConfig<VectorField, FilterFields>>,
376-
): TableDefinition<DocumentType, Indexes, SearchIndexes, VectorIndexes> {
377-
this.stagedVectorIndexes.push({
378-
indexDescriptor: name,
379-
vectorField: indexConfig.vectorField,
380-
dimensions: indexConfig.dimensions,
381-
filterFields: indexConfig.filterFields || [],
382-
});
440+
indexConfig: Expand<
441+
VectorIndexConfig<VectorField, FilterFields> &
442+
IndexOptions & { staged: true }
443+
>,
444+
): TableDefinition<DocumentType, Indexes, SearchIndexes, VectorIndexes>;
445+
446+
vectorIndex<
447+
IndexName extends string,
448+
VectorField extends ExtractFieldPaths<DocumentType>,
449+
FilterFields extends ExtractFieldPaths<DocumentType> = never,
450+
>(
451+
name: IndexName,
452+
indexConfig: Expand<
453+
VectorIndexConfig<VectorField, FilterFields> & IndexOptions
454+
>,
455+
) {
456+
if (indexConfig.staged) {
457+
this.stagedVectorIndexes.push({
458+
indexDescriptor: name,
459+
vectorField: indexConfig.vectorField,
460+
dimensions: indexConfig.dimensions,
461+
filterFields: indexConfig.filterFields || [],
462+
});
463+
} else {
464+
this.vectorIndexes.push({
465+
indexDescriptor: name,
466+
vectorField: indexConfig.vectorField,
467+
dimensions: indexConfig.dimensions,
468+
filterFields: indexConfig.filterFields || [],
469+
});
470+
}
383471
return this;
384472
}
385473

0 commit comments

Comments
 (0)