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
19 changes: 18 additions & 1 deletion website/public/playground/playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { DUCKDB_DIR, DUCKDB_WASM_BYTES } from './duckdb-version.js';
import { ENGINE_DIR, ENGINE_WASM_BYTES } from './engine-version.js';
import { CAPTIONS, EXAMPLE_GROUPS, EXAMPLES, ROWS, SCHEMA, stopLabel } from './examples.js';
import { escape, rendered, resultTable, underlined } from './spans.js';
import { escape, previewLimit, rendered, resultTable, underlined } from './spans.js';

const here = (path) => new URL(path, import.meta.url).href;
const ENGINE_WASM = here(`${ENGINE_DIR}sqlquack_wasm_bg.wasm`);
Expand Down Expand Up @@ -362,6 +362,8 @@ async function execute() {
if (!sql) return;
const ticket = ++running;
setStage('run', 'running', '');
// Whether the error came from the translated statement itself, after the fixture loaded.
let ranStatement = false;
try {
if (!duckdb) await startDuckDB();
const conn = await duckdb.db.connect();
Expand All @@ -371,6 +373,7 @@ async function execute() {
for (const table of tables.tables) await conn.query(table.replace(/^CREATE TABLE/, 'CREATE OR REPLACE TABLE'));
if (rowsBox.value.trim()) await conn.query(rowsBox.value);
const started = performance.now();
ranStatement = true;
// Every value as DuckDB's own text, so decimals and timestamps show exactly.
const result = await conn.query(`SELECT COLUMNS(*)::VARCHAR FROM (${sql}) AS result`);
const elapsed = performance.now() - started;
Expand All @@ -392,6 +395,20 @@ async function execute() {
const failure = JSON.parse((await engine('failure', { message })).result);
if (ticket !== running) return;
const version = duckdb ? `DuckDB ${duckdb.version}` : '';
if (!failure.errno && ranStatement && previewLimit(message)) {
setStage(
'run',
'refused',
'<p class="pg-conversion-note"><strong>A limit of the in-browser DuckDB, not of the translation.</strong> ' +
'This page runs a preview build of DuckDB-WASM, which could not run this translated SQL. SQLQuack translates for ' +
'native DuckDB 2.0: <code>sqlquack convert</code> binds every translated statement in native DuckDB before ' +
'returning it, so run it there to see the result.</p>' +
terminal(`<span class="dim">DuckDB-WASM:</span> ${escape(message)}`),
version,
'not run here',
);
return;
}
if (!failure.errno) {
setStage('run', 'fail', terminal(`<span class="bad">DuckDB:</span> ${escape(message)}`), version);
return;
Expand Down
7 changes: 7 additions & 0 deletions website/public/playground/spans.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,10 @@ export function resultTable(names, columns) {
const head = names.map((name) => `<th>${escape(name)}</th>`).join('');
return { html: `<div class="pg-rows"><table><thead><tr>${head}</tr></thead><tbody>${body.join('')}</tbody></table></div>`, rows };
}

// Whether a DuckDB-WASM error on a translated statement comes from the in-browser engine rather
// than from the rows: a statement it could not bind or plan, or a crash of the WebAssembly engine.
// The playground runs a preview build of DuckDB-WASM; errors about values (conversion, out of
// range) would be raised by native DuckDB too, so they are not counted.
const PREVIEW_LIMITS = /^(Binder|Catalog|Parser|Not implemented|INTERNAL) Error|memory access out of bounds|unreachable|RuntimeError/;
export const previewLimit = (message) => PREVIEW_LIMITS.test(message);
11 changes: 10 additions & 1 deletion website/tests/playground.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { dirname, join } from 'node:path';
import { test } from 'node:test';

import { initSync, analyze, fixture_tables, run_failure } from '../wasm/pkg/sqlquack_wasm.js';
import { rendered, resultTable, underlined } from '../public/playground/spans.js';
import { previewLimit, rendered, resultTable, underlined } from '../public/playground/spans.js';

initSync({ module: readFileSync(new URL('../wasm/pkg/sqlquack_wasm_bg.wasm', import.meta.url)) });

Expand Down Expand Up @@ -119,6 +119,15 @@ test('every example chip stops at the stage its label names, the Run stage inclu
assert.ok(CAPTIONS.refused);
});

test('a run error from the in-browser DuckDB is told apart from an error about the rows', () => {
// What the preview build of DuckDB-WASM reported for a translated DATEADD that native DuckDB 2.0 runs.
assert.ok(previewLimit("Binder Error: No function matches the given name and argument types '+(DATE, INTERVAL)'."));
assert.ok(previewLimit('memory access out of bounds'));
// Errors about values would come from native DuckDB too.
assert.ok(!previewLimit("Conversion Error: Could not convert string 'x' to INT32"));
assert.ok(!previewLimit('Invalid Input Error: Attempting to fetch from an unsuccessful query result'));
});

test('translated SQL is laid out one clause per line unless the compact setting asks for one line', () => {
const sql = "SELECT order_id, note, 'a longer literal' AS label FROM orders WHERE order_id > 1";
const translated = (settings) => JSON.parse(analyze(sql, SCHEMA, JSON.stringify(settings))).translate.statements[0].sql;
Expand Down
Loading