Skip to content

SQL literal#176

Draft
tophtucker wants to merge 13 commits into
mainfrom
toph/sql
Draft

SQL literal#176
tophtucker wants to merge 13 commits into
mainfrom
toph/sql

Conversation

@tophtucker

@tophtucker tophtucker commented Jul 10, 2026

Copy link
Copy Markdown
Member

https://linear.app/observable/issue/DEV-1662/sql-fragments

To do

  • Manually test
  • Remove ! assertions
  • Combine ident and getIquote
  • Think about interaction with DatabaseClient

@Fil
Fil self-requested a review July 13, 2026 18:16
Comment thread src/runtime/stdlib/sql.ts Outdated
cache: Map<SqlFragment | SqlVariant, unknown>
): unknown {
let v: unknown;
if (dialect !== undefined && dialect in variant.variants) v = variant.variants[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.

This addition of dialect !== undefined is for type safety more than runtime behavior.

Comment thread src/runtime/stdlib/index.ts

/** @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.

Comment thread src/runtime/stdlib/sql.ts Outdated
)
);
});
test("chains a view into a recursive query (flight reachability)", () => {

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.

Comment thread src/runtime/index.ts Outdated
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.

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?

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants