Skip to content

Commit 2558a81

Browse files
ldanilekConvex, Inc.
authored andcommitted
allow "_id" filters in index queries (#23979)
allow indexes to be queried with filters on `"_id"`, including the `by_id` index. also adds typescript support for by_id and for `_id` in every index this enables manual pagination (not easily, since you have to do multiple queries, but at least now it's possible) and also makes `db.get` just a special case of `db.query`, which helps with parallelization efforts. added tests. GitOrigin-RevId: 779e62021d30cd9d04386e141f657940eeb2b69c
1 parent 620c149 commit 2558a81

File tree

8 files changed

+42
-18
lines changed

8 files changed

+42
-18
lines changed

crates/common/src/bootstrap_model/index/database_index/indexed_fields.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{
22
collections::HashSet,
33
convert::TryFrom,
44
fmt::Display,
5+
iter,
56
ops::Deref,
67
};
78

@@ -44,6 +45,10 @@ impl IndexedFields {
4445
.expect("Invalid _creationTime field path");
4546
IndexedFields(vec![field_path].into())
4647
}
48+
49+
pub fn iter_with_id(&self) -> impl Iterator<Item = &FieldPath> {
50+
self.iter().chain(iter::once(&*ID_FIELD_PATH))
51+
}
4752
}
4853

4954
impl HeapSize for IndexedFields {

crates/common/src/query.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,13 @@ impl IndexRange {
189189
// NB: `indexed_fields` does not include the implicit `_id` field at the end of
190190
// every index, so this omission prevents the user from using it in an
191191
// index expression.
192-
let index_rank: BTreeMap<_, _> = indexed_fields[..]
193-
.iter()
192+
let index_rank: BTreeMap<_, _> = indexed_fields
193+
.iter_with_id()
194194
.enumerate()
195195
.map(|(i, field_name)| (field_name, i))
196196
.collect();
197197
anyhow::ensure!(
198-
index_rank.len() == indexed_fields.len(),
198+
index_rank.len() == indexed_fields.len() + 1,
199199
"{index_name} has duplicate fields?"
200200
);
201201

@@ -237,7 +237,7 @@ impl IndexRange {
237237

238238
let query_fields = QueryFields(used_paths.clone());
239239

240-
let mut fields_iter = indexed_fields.iter();
240+
let mut fields_iter = indexed_fields.iter_with_id();
241241
for field_path in used_paths {
242242
let matching_field = fields_iter.next().ok_or_else(|| {
243243
invalid_index_range(&index_name, &indexed_fields, &query_fields, &field_path)

crates/isolate/src/tests/basic.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ async fn test_insert_and_get(rt: TestRuntime) -> anyhow::Result<()> {
162162
assert_eq!(id2, id);
163163
must_let!(let Some(field) = obj.get("field"));
164164
assert_eq!(field, &value);
165+
166+
// Get the object by index lookup.
167+
must_let!(let ConvexValue::Object(obj) = t.query("basic:getObjectById", assert_obj!("id" => id.clone())).await?);
168+
must_let!(let Some(id3) = obj.get("_id"));
169+
assert_eq!(id3, id);
165170
Ok(())
166171
}
167172

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export type {
128128
WithoutSystemFields,
129129
WithOptionalSystemFields,
130130
SystemIndexes,
131-
IndexTiebreakerField,
131+
IndexTiebreakerFields,
132132
} from "./system_fields.js";
133133
export { httpRouter, HttpRouter, ROUTABLE_HTTP_METHODS } from "./router.js";
134134
export type { RoutableMethod } from "./router.js";

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,17 @@ describe("DataModelFromSchemaDefinition", () => {
432432
| "property1"
433433
| "property2";
434434
type ExpectedIndexes = {
435-
by_property1: ["property1", "_creationTime"];
436-
by_property1_property2: ["property1", "property2", "_creationTime"];
435+
by_property1: ["property1", "_creationTime", "_id"];
436+
by_property1_property2: [
437+
"property1",
438+
"property2",
439+
"_creationTime",
440+
"_id",
441+
];
437442

438-
// System index
439-
by_creation_time: ["_creationTime"];
443+
// System indexes
444+
by_creation_time: ["_creationTime", "_id"];
445+
by_id: ["_id"];
440446
};
441447
type ExpectedDataModel = {
442448
table: {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {
3838
} from "../server/data_model.js";
3939
import {
4040
IdField,
41-
IndexTiebreakerField,
41+
IndexTiebreakerFields,
4242
SystemFields,
4343
SystemIndexes,
4444
} from "../server/system_fields.js";
@@ -200,7 +200,7 @@ export class TableDefinition<
200200
Indexes &
201201
Record<
202202
IndexName,
203-
[FirstFieldPath, ...RestFieldPaths, IndexTiebreakerField]
203+
[FirstFieldPath, ...RestFieldPaths, ...IndexTiebreakerFields]
204204
>
205205
>,
206206
SearchIndexes,

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,15 @@ export type WithOptionalSystemFields<Document extends GenericDocument> = Expand<
4646
* @public
4747
*/
4848
export type SystemIndexes = {
49-
// We have a system index `by_id` but developers should never have a use
50-
// for querying it (`db.get(id)` is always simpler).
51-
// by_id: ["_id"];
49+
// Note `db.get(id)` is simpler and equivalent to a query on `by_id`.
50+
// Unless the query is being built dynamically, or doing manual pagination.
51+
by_id: ["_id"];
5252

53-
by_creation_time: ["_creationTime"];
53+
by_creation_time: ["_creationTime", "_id"];
5454
};
5555

5656
/**
57-
* Convex automatically appends "_creationTime" to the end of every index to
58-
* break ties if all of the other fields are identical.
57+
* Convex automatically appends "_creationTime" and "_id" to the end of every index to break ties if all other fields are identical.
5958
* @public
6059
*/
61-
export type IndexTiebreakerField = "_creationTime";
60+
export type IndexTiebreakerFields = ["_creationTime", "_id"];

npm-packages/udf-tests/convex/basic.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ export const getObject = query(async ({ db }, { id }: { id: Id<any> }) => {
2222
return db.get(id);
2323
});
2424

25+
export const getObjectById = query(
26+
async ({ db }, { id }: { id: Id<"objects"> }) => {
27+
return db
28+
.query("objects")
29+
.withIndex("by_id", (q) => q.eq("_id", id))
30+
.unique();
31+
},
32+
);
33+
2534
export const insertObject = mutation(async ({ db }, obj) => {
2635
const id = await db.insert("objects", obj);
2736
return await db.get(id);

0 commit comments

Comments
 (0)