feat(bindings): expose new IndexConfig fields in Python and Node.js (#3534)

## Summary

Surfaces the rich per-index metadata added in #3497 to the Python and
Node.js language bindings. Closes #3495.

New optional fields exposed on `IndexConfig` in both bindings:

- `index_uuid` / `indexUuid` — UUID of the first index segment
- `type_url` / `typeUrl` — protobuf type URL for the index
- `created_at` / `createdAt` — creation timestamp (milliseconds since
Unix epoch)
- `num_indexed_rows` / `numIndexedRows` — rows covered by the index
- `num_unindexed_rows` / `numUnindexedRows` — rows not yet indexed
- `size_bytes` / `sizeBytes` — total index file size in bytes
- `num_segments` / `numSegments` — number of index segments
- `index_version` / `indexVersion` — on-disk format version
- `index_details` / `indexDetails` — type-specific JSON details string

All fields are `None`/`undefined` for remote tables (which don't yet
surface this metadata through the server response).

## Changes

- `python/src/index.rs`: extend `IndexConfig` pyclass; update `From`
impl; update `__getitem__`
- `python/python/lancedb/_lancedb.pyi`: add type hints for new fields
- `python/python/tests/test_table.py`: new `test_index_config_fields`
test
- `nodejs/src/table.rs`: extend `IndexConfig` napi struct; update `From`
impl
- `nodejs/__test__/table.test.ts`: new test; update existing `toEqual`
assertions to `expect.objectContaining` to accommodate new fields

## Test plan

- [x] Python: `uv run --extra tests pytest
python/tests/test_table.py::test_index_config_fields`
- [x] Node.js: `pnpm test __test__/table.test.ts`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Will Jones
2026-06-11 13:37:39 -07:00
committed by GitHub
parent 40f3e22600
commit f8caef3aca
10 changed files with 359 additions and 45 deletions

View File

@@ -845,11 +845,13 @@ describe("When creating an index", () => {
expect(fs.readdirSync(indexDir)).toHaveLength(1);
const indices = await tbl.listIndices();
expect(indices.length).toBe(1);
expect(indices[0]).toEqual({
name: "vec_idx",
indexType: "IvfPq",
columns: ["vec"],
});
expect(indices[0]).toEqual(
expect.objectContaining({
name: "vec_idx",
indexType: "IvfPq",
columns: ["vec"],
}),
);
const stats = await tbl.indexStats("vec_idx");
expect(stats).toBeDefined();
@@ -1011,51 +1013,51 @@ describe("When creating an index", () => {
const indices = await nestedTable.listIndices();
expect(indices).toEqual(
expect.arrayContaining([
{
expect.objectContaining({
name: "row_id_idx",
indexType: "BTree",
columns: ["rowId"],
},
{
}),
expect.objectContaining({
name: "row_dash_id_idx",
indexType: "BTree",
columns: ["`row-id`"],
},
{
}),
expect.objectContaining({
name: "top_user_id_idx",
indexType: "BTree",
columns: ["userId"],
},
{
}),
expect.objectContaining({
name: "nested_user_id_idx",
indexType: "BTree",
columns: ["metadata.user_id"],
},
{
}),
expect.objectContaining({
name: "mixed_case_metadata_user_id_idx",
indexType: "BTree",
columns: ["MetaData.userId"],
},
{
}),
expect.objectContaining({
name: "escaped_names_idx",
indexType: "BTree",
columns: ["`meta-data`.`user-id`"],
},
{
}),
expect.objectContaining({
name: "literal_dot_idx",
indexType: "BTree",
columns: ["literal.`a.b`"],
},
{
}),
expect.objectContaining({
name: "image_embedding_idx",
indexType: "IvfPq",
columns: ["image.embedding"],
},
{
}),
expect.objectContaining({
name: "payload_text_idx",
indexType: "FTS",
columns: ["payload.text"],
},
}),
]),
);
@@ -1109,16 +1111,16 @@ describe("When creating an index", () => {
const indicesAfterOptimize = await nestedTable.listIndices();
expect(indicesAfterOptimize).toEqual(
expect.arrayContaining([
{
expect.objectContaining({
name: "mixed_case_metadata_user_id_idx",
indexType: "BTree",
columns: ["MetaData.userId"],
},
{
}),
expect.objectContaining({
name: "image_embedding_idx",
indexType: "IvfPq",
columns: ["image.embedding"],
},
}),
]),
);
});
@@ -1254,11 +1256,13 @@ describe("When creating an index", () => {
expect(fs.readdirSync(indexDir)).toHaveLength(1);
const indices = await tbl.listIndices();
expect(indices.length).toBe(1);
expect(indices[0]).toEqual({
name: "vec_idx",
indexType: "IvfHnswSq",
columns: ["vec"],
});
expect(indices[0]).toEqual(
expect.objectContaining({
name: "vec_idx",
indexType: "IvfHnswSq",
columns: ["vec"],
}),
);
// Search without specifying the column
let rst = await tbl
@@ -1604,6 +1608,35 @@ describe("When creating an index", () => {
expect(rst64Query.toString()).toEqual(rst64Search.toString());
expect(rst64Query.numRows).toBe(2);
});
it("should expose rich metadata fields on IndexConfig", async () => {
await tbl.createIndex("id", { config: Index.btree() });
await tbl.createIndex("vec");
const indicesByName = Object.fromEntries(
(await tbl.listIndices()).map((idx) => [idx.name, idx]),
);
const scalarIdx = indicesByName["id_idx"];
expect(scalarIdx).toBeDefined();
expect(typeof scalarIdx.indexUuid).toBe("string");
expect(scalarIdx.numIndexedRows).toBe(300);
expect(scalarIdx.numUnindexedRows).toBe(0);
expect(scalarIdx.numSegments).toBeGreaterThanOrEqual(1);
expect(scalarIdx.sizeBytes).toBeGreaterThan(0);
// Use toString check to avoid cross-realm instanceof failures with native Date objects
expect(Object.prototype.toString.call(scalarIdx.createdAt)).toBe(
"[object Date]",
);
expect((scalarIdx.createdAt as Date).getTime()).toBeGreaterThan(0);
expect(typeof scalarIdx.indexDetails).toBe("object");
const vectorIdx = indicesByName["vec_idx"];
expect(vectorIdx).toBeDefined();
expect(typeof vectorIdx.indexUuid).toBe("string");
expect(vectorIdx.numIndexedRows).toBe(300);
expect(typeof vectorIdx.indexDetails).toBe("object");
});
});
describe("When querying a table", () => {