Indexes & explain
Indexes are created per collection field and are persisted on disk. They speed up queries like:
{ email: "a@b.com" }{ age: { $gte: 18 } }{ status: { $in: ["active", "trial"] } }
db.createIndex(collection, field, options?)
- TypeScript
- JavaScript
createIndex(collection: string, field: string, options?: { unique?: boolean; include?: string[] }): Promise<void>
createIndex(collection, field, options)
- Builds the index from existing documents.
- After creation, writes keep the index updated.
unique: trueenforces uniqueness for that field.include: string[]creates a covering index. Included fields (plus_id) are stored in the index so projected queries can avoid primary document reads.
Example: create a unique index
- TypeScript
- JavaScript
import { LioranManager } from "@liorandb/core";
const manager = new LioranManager({ rootPath: "./.liorandb" });
const db = await manager.db("app");
const users = db.collection("users");
await db.createIndex("users", "email", { unique: true });
await users.insertOne({ email: "a@b.com" });
console.log(await users.findOne({ email: "a@b.com" }));
await manager.close();
Sandbox output (example)
{ _id: "...", email: "a@b.com", __v: 1 }
import { LioranManager } from "@liorandb/core";
const manager = new LioranManager({ rootPath: "./.liorandb" });
const db = await manager.db("app");
const users = db.collection("users");
await db.createIndex("users", "email", { unique: true });
await users.insertOne({ email: "a@b.com" });
console.log(await users.findOne({ email: "a@b.com" }));
await manager.close();
Sandbox output (example)
{ _id: "...", email: "a@b.com", __v: 1 }
What happens on duplicates? (unique index)
If you insert a second doc with the same indexed value, the write is rejected.
Sandbox output (example)
Error: Unique constraint violation on index "email"
How LioranDB uses indexes
- Query execution tries to pick one best index from your query.
- If no index can be selected, a full scan is used.
- Even with an index, documents are still re-checked against your full query to ensure correctness.
Text indexes (full-text search)
Text indexes are optional and are used by the $text query operator.
db.createTextIndex(collection, field, options?)
createTextIndex(collection: string, field: string, options?: { normalize?: boolean; stopwords?: string[] }): Promise<void>
normalize(default:true): split on non-alphanumeric and lowercase (recommended).stopwords: optional words to ignore.
Querying: $text
$text pre-filters candidates (AND semantics) and then the query continues as usual.
// search all text-indexed fields
await users.find({ $text: { $search: "hello world" } });
// search only specific fields
await users.find({ $text: { $search: "hello world", $fields: ["title", "body"] } });
If you use $text without any text index configured, the query throws UNSUPPORTED_QUERY.
db.explain(collection, query?, options?)
Use explain() to see what the planner did and how much work the query performed.
- TypeScript
- JavaScript
const plan = await db.explain("users", { age: { $gte: 18 } });
console.log(plan);
const plan = await db.explain("users", { age: { $gte: 18 } });
console.log(plan);
Sandbox output (example)
{
indexUsed: "age",
indexType: "btree",
scannedDocuments: 42,
returnedDocuments: 7,
executionTimeMs: 3,
usedFullScan: false,
candidateDocuments: 42
}
Example: compare indexed vs full scan
- TypeScript
- JavaScript
import { LioranManager } from "@liorandb/core";
const manager = new LioranManager({ rootPath: "./.liorandb" });
const db = await manager.db("app");
const users = db.collection("users");
await users.insertMany([
{ email: "a@b.com", age: 21 },
{ email: "c@d.com", age: 35 },
{ email: "e@f.com", age: 10 },
]);
const before = await db.explain("users", { age: { $gte: 18 } });
await db.createIndex("users", "age");
const after = await db.explain("users", { age: { $gte: 18 } });
console.log({ before: before.usedFullScan, after: after.usedFullScan });
await manager.close();
import { LioranManager } from "@liorandb/core";
const manager = new LioranManager({ rootPath: "./.liorandb" });
const db = await manager.db("app");
const users = db.collection("users");
await users.insertMany([
{ email: "a@b.com", age: 21 },
{ email: "c@d.com", age: 35 },
{ email: "e@f.com", age: 10 },
]);
const before = await db.explain("users", { age: { $gte: 18 } });
await db.createIndex("users", "age");
const after = await db.explain("users", { age: { $gte: 18 } });
console.log({ before: before.usedFullScan, after: after.usedFullScan });
await manager.close();
Sandbox output (example)
{ before: true, after: false }