feat: list_bases returns registered table storage prefixes

Return the additional storage bases for the current table snapshot
on native, memory, namespace, and Cloud clients.
This commit is contained in:
geruh
2026-08-20 18:46:07 -07:00
parent 047f431837
commit cfcbcfbc92
14 changed files with 418 additions and 43 deletions
+62
View File
@@ -1028,11 +1028,30 @@ describe("remote connection jobs surface", () => {
});
return;
}
if (path.endsWith("/bases/list/")) {
res.writeHead(200, { "Content-Type": "application/json" }).end(
JSON.stringify({
bases: [
{
path: "s3://bucket/media/",
isDatasetRoot: false,
},
],
}),
);
return;
}
res.writeHead(404).end();
},
async (db) => {
const table = await db.openTable("photos");
await table.addBases({ path: "s3://bucket/media/" });
expect(await table.listBases()).toEqual([
{
path: "s3://bucket/media/",
isDatasetRoot: false,
},
]);
},
);
expect(postedBodies).toEqual([
@@ -1046,4 +1065,47 @@ describe("remote connection jobs surface", () => {
},
]);
});
it("listBases returns a named dataset-root base", async () => {
await withMockDatabase(
(req, res) => {
const path = req.url ?? "";
if (path.endsWith("/describe/")) {
res.writeHead(200, { "Content-Type": "application/json" }).end(
JSON.stringify({
name: "photos",
version: 1,
schema: { fields: [] },
}),
);
return;
}
if (path.endsWith("/bases/list/")) {
res.writeHead(200, { "Content-Type": "application/json" }).end(
JSON.stringify({
bases: [
{
path: "s3://bucket/archive/",
name: "archive",
isDatasetRoot: true,
},
],
}),
);
return;
}
res.writeHead(404).end();
},
async (db) => {
const table = await db.openTable("photos");
expect(await table.listBases()).toEqual([
{
path: "s3://bucket/archive/",
name: "archive",
isDatasetRoot: true,
},
]);
},
);
});
});
+8 -2
View File
@@ -3413,7 +3413,7 @@ describe("table bases", () => {
});
afterEach(() => tmpDir.removeCallback());
it("addBases accepts a file uri", async () => {
it("listBases reflects added bases", async () => {
const conn = await connect(tmpDir.name);
const table = await conn.createEmptyTable(
"photos",
@@ -3421,6 +3421,12 @@ describe("table bases", () => {
);
const media = path.join(tmpDir.name, "media");
fs.mkdirSync(media);
await table.addBases(pathToFileURL(media).toString());
const location = pathToFileURL(media).toString();
expect(await table.listBases()).toEqual([]);
await table.addBases(location);
expect(await table.listBases()).toEqual([
{ path: location, isDatasetRoot: false },
]);
});
});
+7
View File
@@ -591,6 +591,9 @@ export abstract class Table {
bases: string | TableBase | Array<string | TableBase>,
): Promise<void>;
/** Return the additional storage bases for the current table snapshot. */
abstract listBases(): Promise<TableBase[]>;
/**
* Fill the rows of a computed column that hold no value yet.
*
@@ -1230,6 +1233,10 @@ export class LocalTable extends Table {
await this.inner.addBases(normalizeBases(bases));
}
async listBases(): Promise<TableBase[]> {
return await this.inner.listBases();
}
async refreshColumn(column: string): Promise<RefreshColumnResult> {
return await this.inner.refreshColumn(column);
}
+22
View File
@@ -459,6 +459,18 @@ impl Table {
.default_error()
}
#[napi(catch_unwind)]
pub async fn list_bases(&self) -> napi::Result<Vec<TableBase>> {
Ok(self
.inner_ref()?
.list_bases()
.await
.default_error()?
.into_iter()
.map(TableBase::from)
.collect())
}
#[napi(catch_unwind)]
pub async fn drop_columns(&self, columns: Vec<String>) -> napi::Result<DropColumnsResult> {
let col_refs = columns.iter().map(String::as_str).collect::<Vec<_>>();
@@ -725,6 +737,16 @@ pub struct TableBase {
pub is_dataset_root: bool,
}
impl From<LanceTableBase> for TableBase {
fn from(base: LanceTableBase) -> Self {
Self {
path: base.path,
name: base.name,
is_dataset_root: base.is_dataset_root,
}
}
}
#[napi(object)]
/// A description of an index currently configured on a column
pub struct IndexConfig {