demo D232
Live pushdown to DuckDB, query by query
Every sort, filter and page becomes one SQL statement DuckDB answers, with only the window coming back and the generated SQL and timing on screen
createPushdownSource · duckdbAdapter · lastPlan()
Loading a live grid…
The configuration
import * as ng from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
import * as Lattice from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import createLatticeGrid from '@toclocoinc/lattice-grid/modules/angular';
const { createGrid, createPushdownSource, duckdbAdapter } = Lattice;
const { LatticeGridComponent } = createLatticeGrid({ ng, createGrid });
const columns = [
{ field: 'reading_id', title: '#', type: 'number', layout: { width: 110 } },
{ field: 'plant', title: 'Plant', filter: { type: 'set' } },
{ field: 'line', title: 'Line', filter: { type: 'set' } },
{ field: 'product', title: 'Product' },
{ field: 'bore_mm', title: 'Bore', type: 'millimetres', spec: { lower: 9.95, upper: 10.05, target: 10 } },
{ field: 'cycle_seconds', title: 'Cycle', type: 'seconds' },
{ field: 'in_spec', title: 'In spec', type: 'boolean' },
{ field: 'taken_at', title: 'Taken', type: 'dateString' },
];
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, LatticeGridComponent],
template:
'<div style="font-size:13px;color:var(--ink-2);line-height:1.5">{{ status }}</div>' +
'<lattice-grid *ngIf="gridConfig" [config]="gridConfig" style="display:block;height:560px;margin-top:12px"></lattice-grid>' +
'<pre *ngIf="sql" style="margin-top:12px">{{ sql }}</pre>',
})
export class AppComponent implements OnInit, OnDestroy {
status = 'Starting DuckDB in the browser...';
gridConfig: any = null;
sql = '';
private disposed = false;
private connection: any;
private worker: any;
// DuckDB is the caller's engine, loaded from a CDN; the grid loads none of it.
// duckdbAdapter takes the connection you made, so a hundred thousand rows answer
// each filter and sort in SQL while the grid stays dependency-free.
async ngOnInit() {
try {
this.status = 'Loading DuckDB from a CDN...';
const duckdb = await import('https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm/+esm');
const bundle = await duckdb.selectBundle(duckdb.getJsDelivrBundles());
this.worker = await duckdb.createWorker(bundle.mainWorker);
const db = new duckdb.AsyncDuckDB(new duckdb.ConsoleLogger(duckdb.LogLevel.ERROR), this.worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
if (this.disposed) return;
this.connection = await db.connect();
// The engine resolves the URL, so it must be absolute. read_parquet streams
// the file, so a host that honours range requests lets DuckDB read only the
// bytes a query touches.
const file = new URL('/demo-data/readings.parquet', location.origin).href;
const adapter = duckdbAdapter({ connection: this.connection, from: `read_parquet('${file}')` });
// Wrap execute to show the statement DuckDB ran and how long it took.
const inner = adapter.execute.bind(adapter);
adapter.execute = async (query: any, request: any) => {
const started = performance.now();
const result = await inner(query, request);
const ms = Math.round(performance.now() - started);
const f = adapter.sqlFor(query);
if (!this.disposed) this.sql = f.sql + '\n-- ' + ms + ' ms, ' + result.rows.length + ' of ' + (result.total ?? '?') + ' rows';
return result;
};
this.gridConfig = {
rowKey: 'reading_id',
toolPanel: { side: 'left', panels: ['filters', 'columns'] },
columns,
source: createPushdownSource({ adapter, compute: Lattice, pageSize: 200 }),
};
this.status = 'Rows in DuckDB. Filter a line or sort a column and the exact statement it ran shows below.';
} catch (err) {
this.status = 'DuckDB could not start here. It loads from a CDN and needs a browser with WebAssembly and cross-origin isolation.';
}
}
ngOnDestroy() {
this.disposed = true;
try { this.connection?.close?.(); } catch (e) {}
try { this.worker?.terminate?.(); } catch (e) {}
}
}
bootstrapApplication(AppComponent);