Skip to content

Commit

Permalink
Setup DuckDB Wasm with Stimulus for client-side queries
Browse files Browse the repository at this point in the history
  • Loading branch information
murdho committed Jan 26, 2025
1 parent 1fae547 commit 87cc8a8
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 1 deletion.
19 changes: 19 additions & 0 deletions app/javascript/controllers/duckdb_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Controller } from "@hotwired/stimulus"
import { startDuckDB, stopDuckDB } from "helpers/duckdb";

// Connects to data-controller="duckdb"
export default class extends Controller {
static values = {
databasePath: String,
}

async connect() {
this.db = await startDuckDB(this.databasePathValue);
window.duckdb = this.db;
this.dispatch("ready");
}

async disconnect() {
await stopDuckDB(this.db);
}
}
50 changes: 50 additions & 0 deletions app/javascript/helpers/duckdb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as duckdb from "@duckdb/duckdb-wasm";

export async function startDuckDB(databasePath) {
if (!databasePath) {
throw new Error("Cannot start DuckDB, invalid database path: '" + databasePath + "'");
}

const db = await initialize();
await openDatabase(db, databasePath);
return db;
}

export async function stopDuckDB(database) {
if (database) {
await database.close();
}
}

async function initialize() {
const bundles = duckdb.getJsDelivrBundles();
const bundle = await duckdb.selectBundle(bundles);

const worker_url = URL.createObjectURL(
new Blob([`importScripts("${bundle.mainWorker}");`], {
type: "text/javascript"
})
);

const worker = new Worker(worker_url);
const logger = new duckdb.ConsoleLogger();
const db = new duckdb.AsyncDuckDB(logger, worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
URL.revokeObjectURL(worker_url);
return db;
}

async function openDatabase(db, databasePath) {
const response = await fetch(databasePath, {
cache: "default"
});

const buffer = await response.arrayBuffer();
await db.registerFileBuffer("database.duckdb", new Uint8Array(buffer));
await db.open({
path: "database.duckdb",
query: {
castTimestampToDate: true
}
});
}
1 change: 1 addition & 0 deletions app/javascript/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "helpers/duckdb"
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en" data-controller="duckdb" data-duckdb-database-path-value="<%= perspective_database_path %>">
<head>
<title><%= ["spendbetter", content_for(:title)].compact.join(" – ") %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
Expand Down
2 changes: 2 additions & 0 deletions config/importmap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin "@duckdb/duckdb-wasm", to: "https://cdn.jsdelivr.net/npm/@duckdb/[email protected]/+esm"
pin_all_from "app/javascript/controllers", under: "controllers"
pin_all_from "app/javascript/helpers", under: "helpers"
4 changes: 4 additions & 0 deletions vendor/javascript/@duckdb--duckdb-wasm.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions vendor/javascript/apache-arrow.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 87cc8a8

Please sign in to comment.