Skip to content
2 changes: 2 additions & 0 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export type * from "./stdlib/fileAttachment.js";
export {FileAttachment, requireFileRegistration, registerFile} from "./stdlib/fileAttachment.js";
export type * from "./stdlib/interpreter.js";
export {Interpreter} from "./stdlib/interpreter.js";
export type * from "./stdlib/sql.js";
export {sql} from "./stdlib/sql.js";

export class NotebookRuntime {
readonly runtime: Runtime & {fileAttachments: typeof fileAttachments};
Expand Down
22 changes: 18 additions & 4 deletions src/runtime/stdlib/databaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {hash, nameHash} from "../../lib/hash.js";
export type QueryParam = any;

/** @see https://observablehq.com/@observablehq/database-client-specification#%C2%A71 */
export type QueryResult = Record<string, any>[] & {schema: ColumnSchema[]; date: Date};
export type QueryResult<T = Record<string, any>> = T[] & {schema: ColumnSchema[]; date: Date};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to remove the date from here?

Also this type does not cover a resultset that would come back as an arrow table. Are we going to convert to arrow later, from the array of objects?

An idea could be to add a format="arrow" | "json" property on sql cells, that would default to JSON (the current format); if you pass "arrow" a database handler that has this capability creates arrow immediately; any other gets converted to arrow.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go away entirely to be replaced with Arrow.Table.


/** @see https://observablehq.com/@observablehq/database-client-specification#%C2%A72.2 */
export interface ColumnSchema {
Expand Down Expand Up @@ -39,10 +39,24 @@ export interface QueryOptions extends QueryOptionsSpec {
since?: Date;
}

export type SqlDialect =
| "bigquery"
| "databricks"
| "duckdb"
| "mongosql"
| "mssql"
| "mysql"
| "oracle"
| "postgres"
| "snowflake"
| "sql"
| "sqlite";

export interface DatabaseClient {
readonly name: string;
readonly options: QueryOptions;
sql(strings: readonly string[], ...params: QueryParam[]): Promise<QueryResult>;
readonly dialect?: SqlDialect;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO DatabaseClientImpl constructor needs to take a dialect.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to implement this (together with binding to a database) in #177.

sql<T = Record<string, any>>(strings: readonly string[], ...params: QueryParam[]): Promise<QueryResult<T>>; // prettier-ignore
}

export const DatabaseClient = (name: string, options?: QueryOptionsSpec): DatabaseClient => {
Expand All @@ -65,11 +79,11 @@ class DatabaseClientImpl implements DatabaseClient {
options: {value: options, enumerable: true}
});
}
async sql(strings: readonly string[], ...params: QueryParam[]): Promise<QueryResult> {
async sql<T = Record<string, any>>(strings: readonly string[], ...params: QueryParam[]): Promise<QueryResult<T>> {
const path = await this.cachePath(strings, ...params);
const response = await fetch(path);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This’ll need to be something like…

    const [Arrow, response] = await Promise.all([import("https://cdn.jsdelivr.net/npm/apache-arrow@17.0.0/+esm"), fetch(path)]); // prettier-ignore
    if (!response.ok) throw new Error(`failed to fetch: ${path}`);
    return Arrow.tableFromIPC(response);

if (!response.ok) throw new Error(`failed to fetch: ${path}`);
return await response.json().then(revive);
return (await response.json().then(revive)) as QueryResult<T>;
}
async cachePath(strings: readonly string[], ...params: QueryParam[]): Promise<string> {
return `.observable/cache/${await nameHash(this.name)}-${await hash(strings, ...params)}.json`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return `.observable/cache/${await nameHash(this.name)}-${await hash(strings, ...params)}.json`;
return `.observable/cache/${await nameHash(this.name)}-${await hash(strings, ...params)}.arrow`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw should we Promise.all these two awaits?

Expand Down
2 changes: 2 additions & 0 deletions src/runtime/stdlib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import {Mutable} from "./mutable.js";
import * as Promises from "./promises/index.js";
import * as recommendedLibraries from "./recommendedLibraries.js";
import * as sampleDatasets from "./sampleDatasets.js";
import {sql} from "./sql.js";

export const root = document.querySelector("main") ?? document.body;

export const library = {
dark: () => Generators.dark(),
now: () => Generators.now(),
sql: () => sql,
width: () => Generators.width(root),
DatabaseClient: () => DatabaseClient,
FileAttachment: () => FileAttachment,
Expand Down
1 change: 0 additions & 1 deletion src/runtime/stdlib/recommendedLibraries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const mermaid = () => import("./mermaid.js").then((_) => _.mermaid);
export const Plot = () => import("https://cdn.jsdelivr.net/npm/@observablehq/plot/+esm");
export const React = () => import("https://cdn.jsdelivr.net/npm/react/+esm");
export const ReactDOM = () => import("https://cdn.jsdelivr.net/npm/react-dom/+esm");
// export const sql = () => import("observablehq:stdlib/duckdb").then((_) => _.sql);
// export const SQLite = () => import("observablehq:stdlib/sqlite").then((_) => _.default);
// export const SQLiteDatabaseClient = () => import("observablehq:stdlib/sqlite").then((_) => _.SQLiteDatabaseClient);
export const tex = () => import("./tex.js").then((_) => _.tex);
Expand Down
Loading