Skip to content

Commit c780dab

Browse files
authored
SQLite (#212)
* SQLite * return null from queryRow * pass params as-is * better describe * simpler table * hyperscript-ish
1 parent da3f422 commit c780dab

File tree

7 files changed

+129
-71
lines changed

7 files changed

+129
-71
lines changed

src/fileAttachment.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {require as requireDefault} from "d3-require";
2+
import sqlite, {SQLiteDatabaseClient} from "./sqlite.js";
23

34
async function remote_fetch(file) {
45
const response = await fetch(await file.url());
@@ -56,6 +57,11 @@ class FileAttachment {
5657
i.src = url;
5758
});
5859
}
60+
async sqlite() {
61+
const [SQL, buffer] = await Promise.all([sqlite(requireDefault), this.arrayBuffer()]);
62+
const db = new SQL.Database(new Uint8Array(buffer));
63+
return new SQLiteDatabaseClient(db);
64+
}
5965
}
6066

6167
export function NoFileAttachments(name) {

src/library.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import now from "./now.js";
1010
import Promises from "./promises/index.js";
1111
import resolve from "./resolve.js";
1212
import requirer from "./require.js";
13+
import SQLite from "./sqlite.js";
1314
import svg from "./svg.js";
1415
import tex from "./tex.js";
1516
import vegalite from "./vegalite.js";
@@ -26,18 +27,19 @@ export default Object.assign(function Library(resolver) {
2627
Mutable: () => Mutable,
2728
Plot: () => require("@observablehq/[email protected]/dist/plot.umd.min.js"),
2829
Promises: () => Promises,
30+
SQLite: () => SQLite(require),
2931
_: () => require("[email protected]/lodash.min.js"),
3032
d3: () => require("[email protected]/dist/d3.min.js"),
3133
dot: () => require("@observablehq/[email protected]/dist/graphviz.min.js"),
3234
htl: () => require("[email protected]/dist/htl.min.js"),
3335
html: () => html,
34-
md: md(require),
36+
md: () => md(require),
3537
now: now,
3638
require: () => require,
3739
resolve: () => resolve,
3840
svg: () => svg,
39-
tex: tex(require),
40-
vl: vegalite(require),
41+
tex: () => tex(require),
42+
vl: () => vegalite(require),
4143
width: width
4244
}));
4345
}, {resolve: requireDefault.resolve});

src/md.js

+37-40
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,44 @@
11
import template from "./template.js";
22

3-
const HL_ROOT =
4-
"https://cdn.jsdelivr.net/npm/@observablehq/[email protected]/";
3+
const HL_ROOT = "https://cdn.jsdelivr.net/npm/@observablehq/[email protected]/";
54

65
export default function(require) {
7-
return function() {
8-
return require("[email protected]/marked.min.js").then(function(marked) {
9-
return template(
10-
function(string) {
11-
var root = document.createElement("div");
12-
root.innerHTML = marked(string, {langPrefix: ""}).trim();
13-
var code = root.querySelectorAll("pre code[class]");
14-
if (code.length > 0) {
15-
require(HL_ROOT + "highlight.min.js").then(function(hl) {
16-
code.forEach(function(block) {
17-
function done() {
18-
hl.highlightBlock(block);
19-
block.parentNode.classList.add("observablehq--md-pre");
20-
}
21-
if (hl.getLanguage(block.className)) {
22-
done();
23-
} else {
24-
require(HL_ROOT + "async-languages/index.js")
25-
.then(index => {
26-
if (index.has(block.className)) {
27-
return require(HL_ROOT +
28-
"async-languages/" +
29-
index.get(block.className)).then(language => {
30-
hl.registerLanguage(block.className, language);
31-
});
32-
}
33-
})
34-
.then(done, done);
35-
}
36-
});
6+
return require("[email protected]/marked.min.js").then(function(marked) {
7+
return template(
8+
function(string) {
9+
var root = document.createElement("div");
10+
root.innerHTML = marked(string, {langPrefix: ""}).trim();
11+
var code = root.querySelectorAll("pre code[class]");
12+
if (code.length > 0) {
13+
require(HL_ROOT + "highlight.min.js").then(function(hl) {
14+
code.forEach(function(block) {
15+
function done() {
16+
hl.highlightBlock(block);
17+
block.parentNode.classList.add("observablehq--md-pre");
18+
}
19+
if (hl.getLanguage(block.className)) {
20+
done();
21+
} else {
22+
require(HL_ROOT + "async-languages/index.js")
23+
.then(index => {
24+
if (index.has(block.className)) {
25+
return require(HL_ROOT +
26+
"async-languages/" +
27+
index.get(block.className)).then(language => {
28+
hl.registerLanguage(block.className, language);
29+
});
30+
}
31+
})
32+
.then(done, done);
33+
}
3734
});
38-
}
39-
return root;
40-
},
41-
function() {
42-
return document.createElement("div");
35+
});
4336
}
44-
);
45-
});
46-
};
37+
return root;
38+
},
39+
function() {
40+
return document.createElement("div");
41+
}
42+
);
43+
});
4744
}

src/sqlite.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export default async function sqlite(require) {
2+
const sql = await require("[email protected]/dist/sql-wasm.js");
3+
return sql({locateFile: file => `https://cdn.jsdelivr.net/npm/[email protected]/dist/${file}`});
4+
}
5+
6+
export class SQLiteDatabaseClient {
7+
constructor(db) {
8+
Object.defineProperties(this, {
9+
_db: {value: db}
10+
});
11+
}
12+
async query(query, params) {
13+
return await exec(this._db, query, params);
14+
}
15+
async queryRow(query, params) {
16+
return (await this.query(query, params))[0] || null;
17+
}
18+
async explain(query, params) {
19+
const rows = await this.query(`EXPLAIN QUERY PLAN ${query}`, params);
20+
return element("pre", {className: "observablehq--inspect"}, [
21+
text(rows.map(row => row.detail).join("\n"))
22+
]);
23+
}
24+
async describe(object) {
25+
const rows = await (object === undefined
26+
? this.query(`SELECT name FROM sqlite_master WHERE type = 'table'`)
27+
: this.query(`SELECT * FROM pragma_table_info(?)`, [object]));
28+
if (!rows.length) throw new Error("Not found");
29+
const {columns} = rows;
30+
return element("table", {value: rows}, [
31+
element("thead", [element("tr", columns.map(c => element("th", [text(c)])))]),
32+
element("tbody", rows.map(r => element("tr", columns.map(c => element("td", [text(r[c])])))))
33+
]);
34+
}
35+
}
36+
37+
async function exec(db, query, params) {
38+
const [result] = await db.exec(query, params);
39+
if (!result) return [];
40+
const {columns, values} = result;
41+
const rows = values.map(row => Object.fromEntries(row.map((value, i) => [columns[i], value])));
42+
rows.columns = columns;
43+
return rows;
44+
}
45+
46+
function element(name, props, children) {
47+
if (arguments.length === 2) children = props, props = undefined;
48+
const element = document.createElement(name);
49+
if (props !== undefined) for (const p in props) element[p] = props[p];
50+
if (children !== undefined) for (const c of children) element.appendChild(c);
51+
return element;
52+
}
53+
54+
function text(value) {
55+
return document.createTextNode(value);
56+
}

src/tex.js

+17-19
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,23 @@ function style(href) {
1111
});
1212
}
1313

14-
export default function(require) {
15-
return function() {
16-
return Promise.all([
17-
require("@observablehq/[email protected]/dist/katex.min.js"),
18-
require.resolve("@observablehq/[email protected]/dist/katex.min.css").then(style)
19-
]).then(function(values) {
20-
var katex = values[0], tex = renderer();
14+
export default function tex(require) {
15+
return Promise.all([
16+
require("@observablehq/[email protected]/dist/katex.min.js"),
17+
require.resolve("@observablehq/[email protected]/dist/katex.min.css").then(style)
18+
]).then(function(values) {
19+
var katex = values[0], tex = renderer();
2120

22-
function renderer(options) {
23-
return function() {
24-
var root = document.createElement("div");
25-
katex.render(raw.apply(String, arguments), root, options);
26-
return root.removeChild(root.firstChild);
27-
};
28-
}
21+
function renderer(options) {
22+
return function() {
23+
var root = document.createElement("div");
24+
katex.render(raw.apply(String, arguments), root, options);
25+
return root.removeChild(root.firstChild);
26+
};
27+
}
2928

30-
tex.options = renderer;
31-
tex.block = renderer({displayMode: true});
32-
return tex;
33-
});
34-
};
29+
tex.options = renderer;
30+
tex.block = renderer({displayMode: true});
31+
return tex;
32+
});
3533
}

src/vegalite.js

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
export default function vl(require) {
2-
return async () => {
3-
const [vega, vegalite, api] = await Promise.all([
4-
"[email protected]/build/vega.min.js",
5-
"[email protected]/build/vega-lite.min.js",
6-
"[email protected]/build/vega-lite-api.min.js"
7-
].map(module => require(module)));
8-
return api.register(vega, vegalite);
9-
};
1+
export default async function vl(require) {
2+
const [vega, vegalite, api] = await Promise.all([
3+
"[email protected]/build/vega.min.js",
4+
"[email protected]/build/vega-lite.min.js",
5+
"[email protected]/build/vega-lite-api.min.js"
6+
].map(module => require(module)));
7+
return api.register(vega, vegalite);
108
}

test/index-test.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ test("new Library returns a library with the expected keys", async t => {
1111
"Mutable",
1212
"Plot",
1313
"Promises",
14+
"SQLite",
1415
"_",
1516
"d3",
1617
"dot",

0 commit comments

Comments
 (0)