Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/duckdb-wasm-shell/crate/src/duckdb/async_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ extern "C" {
conn: ConnectionID,
) -> Result<JsValue, JsValue>;

#[wasm_bindgen(catch, method, js_name = "registerOPFSFileName")]
async fn register_opfs_file_name(
this: &JsAsyncDuckDB,
text: &str,
) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch, method, js_name = "collectFileStatistics")]
async fn collect_file_statistics(
this: &JsAsyncDuckDB,
Expand Down Expand Up @@ -158,6 +163,14 @@ impl AsyncDuckDB {
Ok(())
}

pub async fn register_opfs_file_name(
&self,
file: &str,
) -> Result<(), js_sys::Error> {
self.bindings.register_opfs_file_name(file).await?;
Ok(())
}

/// Enable file statistics
pub async fn export_file_statistics(
&self,
Expand Down
20 changes: 20 additions & 0 deletions packages/duckdb-wasm-shell/crate/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,18 @@ impl Shell {
Shell::with(|s| s.write(&buffer));
}

pub fn register_opfs_file_name(name: &str) {
let db_ptr = Shell::with(|s| s.db.clone());
let name_copy = name.to_string();
spawn_local(async move {
let db = match db_ptr {
Some(ref db) => db.read().unwrap(),
None => return,
};
db.register_opfs_file_name(&name_copy).await.unwrap();
});
}

pub fn collect_file_statistics(name: &str, enable: bool) {
let db_ptr = Shell::with(|s| s.db.clone());
let name_copy = name.to_string();
Expand Down Expand Up @@ -532,6 +544,13 @@ impl Shell {
}
}
}
"register" => {
let filename = args[subcmd.len()..].trim();
db.register_opfs_file_name(filename).await.unwrap();
Shell::with_mut(|s| {
s.writeln(&format!("Registering OPFS file handle for: {}", filename))
});
}
"track" => {
let filename = args[subcmd.len()..].trim();
db.collect_file_statistics(filename, true).await.unwrap();
Expand Down Expand Up @@ -666,6 +685,7 @@ impl Shell {
".files drop Drop all files.\r\n",
".files drop $FILE Drop a single file.\r\n",
".files track $FILE Collect file statistics.\r\n",
".files register $FILE Register OPFS file handle.\r\n",
".files paging $FILE Show file paging.\r\n",
".files reads $FILE Show file reads.\r\n",
".open $FILE Open database file.\r\n",
Expand Down
5 changes: 5 additions & 0 deletions packages/duckdb-wasm-shell/crate/src/shell_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ pub fn writeln(text: &str) {
Shell::with(|s| s.writeln(text));
}

#[wasm_bindgen(js_name = "registerOPFSFileName")]
pub fn register_opfs_file_name(name: &str) {
Shell::register_opfs_file_name(name);
}

#[wasm_bindgen(js_name = "collectFileStatistics")]
pub fn collect_file_statistics(name: &str, enable: bool) {
Shell::collect_file_statistics(name, enable);
Expand Down
21 changes: 20 additions & 1 deletion packages/duckdb-wasm/src/bindings/bindings_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,19 @@ export abstract class DuckDBBindingsBase implements DuckDBBindings {
}
dropResponseBuffers(this.mod);
}
public async prepareFileHandle(fileName: string, protocol: DuckDBDataProtocol): Promise<void> {
if (protocol === DuckDBDataProtocol.BROWSER_FSACCESS && this._runtime.prepareFileHandles) {
const list = await this._runtime.prepareFileHandles([fileName], DuckDBDataProtocol.BROWSER_FSACCESS);
for (const item of list) {
const { handle, path: filePath, fromCached } = item;
if (!fromCached && handle.getSize()) {
await this.registerFileHandleAsync(filePath, handle, DuckDBDataProtocol.BROWSER_FSACCESS, true);
}
}
return;
}
throw new Error(`prepareFileHandle: unsupported protocol ${protocol}`);
}
/** Prepare a file handle that could only be acquired aschronously */
public async prepareDBFileHandle(path: string, protocol: DuckDBDataProtocol): Promise<void> {
if (protocol === DuckDBDataProtocol.BROWSER_FSACCESS && this._runtime.prepareDBFileHandle) {
Expand Down Expand Up @@ -601,8 +614,14 @@ export abstract class DuckDBBindingsBase implements DuckDBBindings {
dropResponseBuffers(this.mod);
return copy;
}

/** Enable tracking of file statistics */
public registerOPFSFileName(file: string): Promise<void> {
if (file.startsWith("opfs://")) {
return this.prepareFileHandle(file, DuckDBDataProtocol.BROWSER_FSACCESS);
} else {
throw new Error("Not an OPFS file name: " + file);
}
}
public collectFileStatistics(file: string, enable: boolean): void {
const [s, d, n] = callSRet(this.mod, 'duckdb_web_collect_file_stats', ['string', 'boolean'], [file, enable]);
if (s !== StatusCode.SUCCESS) {
Expand Down
2 changes: 2 additions & 0 deletions packages/duckdb-wasm/src/bindings/bindings_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ export interface DuckDBBindings {
protocol: DuckDBDataProtocol,
directIO: boolean,
): Promise<HandleType>;
prepareFileHandle(path: string, protocol: DuckDBDataProtocol): Promise<void>;
prepareDBFileHandle(path: string, protocol: DuckDBDataProtocol): Promise<void>;
globFiles(path: string): WebFile[];
dropFile(name: string): void;
dropFiles(): void;
flushFiles(): void;
copyFileToPath(name: string, path: string): void;
copyFileToBuffer(name: string): Uint8Array;
registerOPFSFileName(file: string): void;
collectFileStatistics(file: string, enable: boolean): void;
exportFileStatistics(file: string): FileStatistics;
}
2 changes: 2 additions & 0 deletions packages/duckdb-wasm/src/bindings/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ export interface DuckDBRuntime {
removeFile(mod: DuckDBModule, pathPtr: number, pathLen: number): void;

// Prepare a file handle that could only be acquired aschronously
prepareFileHandle?: (path: string, protocol: DuckDBDataProtocol) => Promise<PreparedDBFileHandle[]>;
prepareFileHandles?: (path: string[], protocol: DuckDBDataProtocol) => Promise<PreparedDBFileHandle[]>;
prepareDBFileHandle?: (path: string, protocol: DuckDBDataProtocol) => Promise<PreparedDBFileHandle[]>;

// Call a scalar UDF function
Expand Down
24 changes: 20 additions & 4 deletions packages/duckdb-wasm/src/bindings/runtime_browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,24 @@ import * as udf from './udf_runtime';
const OPFS_PREFIX_LEN = 'opfs://'.length;
const PATH_SEP_REGEX = /\/|\\/;


export const BROWSER_RUNTIME: DuckDBRuntime & {
_files: Map<string, any>;
_fileInfoCache: Map<number, DuckDBFileInfo>;
_globalFileInfo: DuckDBGlobalFileInfo | null;
_preparedHandles: Record<string, FileSystemSyncAccessHandle>;
_opfsRoot: FileSystemDirectoryHandle | null;

getFileInfo(mod: DuckDBModule, fileId: number): DuckDBFileInfo | null;
getGlobalFileInfo(mod: DuckDBModule): DuckDBGlobalFileInfo | null;
assignOPFSRoot(): Promise<void>;
} = {
_files: new Map<string, any>(),
_fileInfoCache: new Map<number, DuckDBFileInfo>(),
_udfFunctions: new Map(),
_globalFileInfo: null,
_preparedHandles: {} as any,
_opfsRoot: null,

getFileInfo(mod: DuckDBModule, fileId: number): DuckDBFileInfo | null {
try {
Expand Down Expand Up @@ -101,11 +105,15 @@ export const BROWSER_RUNTIME: DuckDBRuntime & {
return null;
}
},

async assignOPFSRoot(): Promise<void> {
if (!BROWSER_RUNTIME._opfsRoot) {
BROWSER_RUNTIME._opfsRoot = await navigator.storage.getDirectory();
}
},
/** Prepare a file handle that could only be acquired aschronously */
async prepareDBFileHandle(dbPath: string, protocol: DuckDBDataProtocol): Promise<PreparedDBFileHandle[]> {
async prepareFileHandles(filePaths: string[], protocol: DuckDBDataProtocol): Promise<PreparedDBFileHandle[]> {
if (protocol === DuckDBDataProtocol.BROWSER_FSACCESS) {
const filePaths = [dbPath, `${dbPath}.wal`];
await BROWSER_RUNTIME.assignOPFSRoot();
const prepare = async (path: string): Promise<PreparedDBFileHandle> => {
if (BROWSER_RUNTIME._files.has(path)) {
return {
Expand All @@ -114,7 +122,7 @@ export const BROWSER_RUNTIME: DuckDBRuntime & {
fromCached: true,
};
}
const opfsRoot = await navigator.storage.getDirectory();
const opfsRoot = BROWSER_RUNTIME._opfsRoot!;
let dirHandle: FileSystemDirectoryHandle = opfsRoot;
// check if mkdir -p is needed
const opfsPath = path.slice(OPFS_PREFIX_LEN);
Expand Down Expand Up @@ -156,6 +164,14 @@ export const BROWSER_RUNTIME: DuckDBRuntime & {
}
return result;
}
throw new Error(`Unsupported protocol ${protocol} for paths ${filePaths} with protocol ${protocol}`);
},
/** Prepare a file handle that could only be acquired aschronously */
async prepareDBFileHandle(dbPath: string, protocol: DuckDBDataProtocol): Promise<PreparedDBFileHandle[]> {
if (protocol === DuckDBDataProtocol.BROWSER_FSACCESS && this.prepareFileHandles) {
const filePaths = [dbPath, `${dbPath}.wal`];
return this.prepareFileHandles(filePaths, protocol);
}
throw new Error(`Unsupported protocol ${protocol} for path ${dbPath} with protocol ${protocol}`);
},

Expand Down
10 changes: 10 additions & 0 deletions packages/duckdb-wasm/src/parallel/async_bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export class AsyncDuckDB implements AsyncDuckDBBindings {
switch (task.type) {
case WorkerRequestType.CLOSE_PREPARED:
case WorkerRequestType.COLLECT_FILE_STATISTICS:
case WorkerRequestType.REGISTER_OPFS_FILE_NAME:
case WorkerRequestType.COPY_FILE_TO_PATH:
case WorkerRequestType.DISCONNECT:
case WorkerRequestType.DROP_FILE:
Expand Down Expand Up @@ -546,6 +547,15 @@ export class AsyncDuckDB implements AsyncDuckDBBindings {
await this.postTask(task, []);
}

/** Enable file statistics */
public async registerOPFSFileName(name: string): Promise<void> {
const task = new WorkerTask<WorkerRequestType.REGISTER_OPFS_FILE_NAME, [string], null>(
WorkerRequestType.REGISTER_OPFS_FILE_NAME,
[name],
);
await this.postTask(task, []);
}

/** Enable file statistics */
public async collectFileStatistics(name: string, enable: boolean): Promise<void> {
const task = new WorkerTask<WorkerRequestType.COLLECT_FILE_STATISTICS, [string, boolean], null>(
Expand Down
5 changes: 5 additions & 0 deletions packages/duckdb-wasm/src/parallel/worker_dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,11 @@ export abstract class AsyncDuckDBDispatcher implements Logger {
this.sendOK(request);
break;

case WorkerRequestType.REGISTER_OPFS_FILE_NAME:
this._bindings.registerOPFSFileName(request.data[0]);
this.sendOK(request);
break;

case WorkerRequestType.EXPORT_FILE_STATISTICS: {
this.postMessage(
{
Expand Down
3 changes: 3 additions & 0 deletions packages/duckdb-wasm/src/parallel/worker_request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum WorkerRequestType {
CANCEL_PENDING_QUERY = 'CANCEL_PENDING_QUERY',
CLOSE_PREPARED = 'CLOSE_PREPARED',
COLLECT_FILE_STATISTICS = 'COLLECT_FILE_STATISTICS',
REGISTER_OPFS_FILE_NAME = 'REGISTER_OPFS_FILE_NAME',
CONNECT = 'CONNECT',
COPY_FILE_TO_BUFFER = 'COPY_FILE_TO_BUFFER',
COPY_FILE_TO_PATH = 'COPY_FILE_TO_PATH',
Expand Down Expand Up @@ -108,6 +109,7 @@ export type WorkerRequestVariant =
| WorkerRequest<WorkerRequestType.CLOSE_PREPARED, [ConnectionID, StatementID]>
| WorkerRequest<WorkerRequestType.CANCEL_PENDING_QUERY, number>
| WorkerRequest<WorkerRequestType.COLLECT_FILE_STATISTICS, [string, boolean]>
| WorkerRequest<WorkerRequestType.REGISTER_OPFS_FILE_NAME, [string]>
| WorkerRequest<WorkerRequestType.CONNECT, null>
| WorkerRequest<WorkerRequestType.COPY_FILE_TO_BUFFER, string>
| WorkerRequest<WorkerRequestType.COPY_FILE_TO_PATH, [string, string]>
Expand Down Expand Up @@ -166,6 +168,7 @@ export type WorkerResponseVariant =

export type WorkerTaskVariant =
| WorkerTask<WorkerRequestType.COLLECT_FILE_STATISTICS, [string, boolean], null>
| WorkerTask<WorkerRequestType.REGISTER_OPFS_FILE_NAME, [string], null>
| WorkerTask<WorkerRequestType.CLOSE_PREPARED, [number, number], null>
| WorkerTask<WorkerRequestType.CONNECT, null, ConnectionID>
| WorkerTask<WorkerRequestType.COPY_FILE_TO_BUFFER, string, Uint8Array>
Expand Down
Loading