Skip to content

Commit 5f33e13

Browse files
committed
feat: implement unregisterFile method
Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
1 parent 11395f5 commit 5f33e13

File tree

2 files changed

+140
-3
lines changed

2 files changed

+140
-3
lines changed

packages/duckdb/src/duckdb.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,61 @@ export class DuckDB extends MainModuleEx<MainModule> {
229229
registerFile(path: string, content: Uint8Array): void {
230230
const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
231231
const split = normalizedPath.lastIndexOf("/");
232-
const dir = split > 0 ? normalizedPath.substring(0, split) : "/";
233-
if (dir.length > 1 && this._module.FS_createPath) {
234-
this._module.FS_createPath(dir, true, true);
232+
if (split > 0) {
233+
const dir = normalizedPath.substring(0, split);
234+
const parts = dir.split("/");
235+
let currentPath = "/";
236+
for (const part of parts) {
237+
if (part && this._module.FS_createPath) {
238+
this._module.FS_createPath(currentPath, part, true, true);
239+
currentPath = currentPath === "/" ? "/" + part : currentPath + "/" + part;
240+
}
241+
}
235242
}
236243
this._module.FS_createDataFile(normalizedPath, undefined, content, true, true, true);
237244
}
238245

246+
/**
247+
* Unregisters and removes a file from the virtual file system.
248+
*
249+
* This removes a file that was previously registered using {@link registerFile} or
250+
* {@link registerFileString}. Once unregistered, the file will no longer be accessible
251+
* to DuckDB queries.
252+
*
253+
* The path is normalized to remove leading slashes before removal.
254+
*
255+
* @param path - The path of the file to remove (e.g., "data/users.csv")
256+
*
257+
* @example
258+
* ```ts
259+
* const duckdb = await DuckDB.load();
260+
*
261+
* // Register a file
262+
* duckdb.registerFileString("temp.csv", "id,name\n1,Alice");
263+
*
264+
* const connection = duckdb.connect();
265+
* let result = connection.query("SELECT * FROM read_csv_auto('temp.csv')");
266+
* console.log(result.rowCount()); // 1
267+
* result.delete();
268+
*
269+
* // Unregister the file
270+
* duckdb.unregisterFile("temp.csv");
271+
*
272+
* // File is no longer accessible
273+
* try {
274+
* result = connection.query("SELECT * FROM read_csv_auto('temp.csv')");
275+
* } catch (error) {
276+
* console.error("File not found"); // Error: file not accessible
277+
* }
278+
*
279+
* connection.delete();
280+
* ```
281+
*/
282+
unregisterFile(path: string): void {
283+
const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
284+
this._module.FS_unlink(normalizedPath);
285+
}
286+
239287
/**
240288
* Registers a text file in the virtual file system.
241289
*

packages/duckdb/tests/duckdb.spec.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,95 @@ describe("duckdb", () => {
357357
result.delete();
358358
con.delete();
359359
});
360+
361+
it("can unregister a file", () => {
362+
const con = duckdb.connect();
363+
const data = [{ id: 1, value: "test" }];
364+
365+
// Register file
366+
duckdb.registerFileString("temp.json", JSON.stringify(data));
367+
368+
// Verify file is accessible
369+
const result1 = con.query("SELECT * FROM read_json_auto('temp.json')");
370+
expect(Number(result1.rowCount())).toBe(1);
371+
result1.delete();
372+
373+
// Unregister file
374+
duckdb.unregisterFile("temp.json");
375+
376+
// Verify file is no longer accessible
377+
const result2 = con.query("SELECT * FROM read_json_auto('temp.json')");
378+
expect(result2.hasError()).toBe(true);
379+
expect(result2.getError()).toContain("temp.json");
380+
result2.delete();
381+
382+
con.delete();
383+
});
384+
385+
it("handles unregisterFile with leading slash", () => {
386+
const con = duckdb.connect();
387+
388+
// Register without leading slash
389+
duckdb.registerFileString("test.csv", "id\n1");
390+
391+
// Verify it's accessible
392+
const result1 = con.query("SELECT * FROM read_csv_auto('test.csv')");
393+
expect(Number(result1.rowCount())).toBe(1);
394+
result1.delete();
395+
396+
// Unregister with leading slash
397+
duckdb.unregisterFile("/test.csv");
398+
399+
// Verify file is no longer accessible
400+
const result2 = con.query("SELECT * FROM read_csv_auto('test.csv')");
401+
expect(result2.hasError()).toBe(true);
402+
result2.delete();
403+
404+
con.delete();
405+
});
406+
407+
it("can reregister a file after unregistering", () => {
408+
const con = duckdb.connect();
409+
const data1 = [{ value: "first" }];
410+
const data2 = [{ value: "second" }];
411+
412+
// Register first version
413+
duckdb.registerFileString("reuse.json", JSON.stringify(data1));
414+
const result1 = con.query("SELECT * FROM read_json_auto('reuse.json')");
415+
expect(result1.getValue(0, 0)).toBe("first");
416+
result1.delete();
417+
418+
// Unregister
419+
duckdb.unregisterFile("reuse.json");
420+
421+
// Register new version with same name
422+
duckdb.registerFileString("reuse.json", JSON.stringify(data2));
423+
const result2 = con.query("SELECT * FROM read_json_auto('reuse.json')");
424+
expect(result2.getValue(0, 0)).toBe("second");
425+
result2.delete();
426+
427+
con.delete();
428+
});
429+
430+
it("unregisterFile handles nested paths", () => {
431+
const con = duckdb.connect();
432+
433+
// Register file with nested path
434+
duckdb.registerFileString("data/nested/file.json", JSON.stringify([{ id: 42 }]));
435+
436+
const result1 = con.query("SELECT * FROM read_json_auto('data/nested/file.json')");
437+
expect(Number(result1.rowCount())).toBe(1);
438+
result1.delete();
439+
440+
// Unregister nested file
441+
duckdb.unregisterFile("data/nested/file.json");
442+
443+
const result2 = con.query("SELECT * FROM read_json_auto('data/nested/file.json')");
444+
expect(result2.hasError()).toBe(true);
445+
result2.delete();
446+
447+
con.delete();
448+
});
360449
});
361450

362451
describe("ErrorData handling", () => {

0 commit comments

Comments
 (0)