Lattice Grid Buy a licence

demo D232

Live pushdown to DuckDB, query by query in React

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()

This grid turns every sort, filter and page into a single SQL statement DuckDB answers, with only the visible window coming back, and shows the generated SQL and its timing. It makes the query-per-interaction model visible, so you see exactly what the grid asks the database for.

Building…
Loading a live grid…

This is the React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.

The configuration

<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.108.0/lattice-grid.min.css">

<div id="app"></div>

<script type="module">
  import React from 'https://esm.sh/react@18';
  import { createRoot } from 'https://esm.sh/react-dom@18/client';
  import * as Lattice from 'https://cdn.latticegrid.dev/lattice-grid@1.108.0/lattice-grid.esm.min.js';
  import createLatticeGrid from 'https://cdn.latticegrid.dev/lattice-grid@1.108.0/modules/react.esm.min.js';

  const { createGrid, createPushdownSource, duckdbAdapter } = Lattice;
  const LatticeGrid = createLatticeGrid({ React, 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' },
  ];

  function App() {
    const [source, setSource] = React.useState(null);
    const [status, setStatus] = React.useState('Starting DuckDB in the browser...');
    const [sql, setSql] = React.useState('');

    // 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.
    React.useEffect(() => {
      let disposed = false, connection, worker;
      (async () => {
        try {
          setStatus('Loading DuckDB from a CDN...');
          const duckdb = await import('https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/+esm');
          const bundle = await duckdb.selectBundle(duckdb.getJsDelivrBundles());
          worker = await duckdb.createWorker(bundle.mainWorker);
          const db = new duckdb.AsyncDuckDB(new duckdb.ConsoleLogger(duckdb.LogLevel.ERROR), worker);
          await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
          if (disposed) return;
          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, 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, request) => {
            const started = performance.now();
            const result = await inner(query, request);
            const ms = Math.round(performance.now() - started);
            const f = adapter.sqlFor(query);
            if (!disposed) setSql(f.sql + '\n-- ' + ms + ' ms, ' + result.rows.length + ' of ' + (result.total ?? '?') + ' rows');
            return result;
          };

          setSource(createPushdownSource({
            adapter, compute: Lattice, pageSize: 200,   // only the visible window comes back, per interaction
          }));
          setStatus('100,000 rows in DuckDB. Filter a line or sort a column and the exact statement it ran shows below.');
        } catch (err) {
          setStatus('DuckDB could not start here. It loads from a CDN and needs a browser with WebAssembly and cross-origin isolation.');
        }
      })();
      return () => { disposed = true; try { connection?.close?.(); } catch (e) {} try { worker?.terminate?.(); } catch (e) {} };
    }, []);

    return (
      <>
        <div style={{ fontSize: '13px', color: 'var(--ink-2)', lineHeight: 1.5 }}>{status}</div>
        {source && (
          <LatticeGrid
            rowKey="reading_id"
            toolPanel={{ side: 'left', panels: ['filters', 'columns'] }}
            columns={columns}
            source={source}
            style={{ height: '560px', marginTop: '12px' }}
          />
        )}
        {sql && <pre style={{ marginTop: '12px' }}>{sql}</pre>}
      </>
    );
  }

  createRoot(document.getElementById('app')).render(<App />);
</script>