From fdd3eb3ab39d7042d0e665e59bd9b96f0382540f Mon Sep 17 00:00:00 2001 From: Diego Imbert Date: Wed, 26 Nov 2025 17:01:27 +0100 Subject: [PATCH] sqk function for ducklake --- typescript-client/build.jsr.sh | 2 +- typescript-client/build.sh | 2 +- typescript-client/client.d.ts | 92 ++++++++++++++++++++++++++------- typescript-client/client.ts | 2 +- typescript-client/sqlUtils.d.ts | 3 +- typescript-client/sqlUtils.ts | 46 ++++++++++++++--- 6 files changed, 117 insertions(+), 30 deletions(-) diff --git a/typescript-client/build.jsr.sh b/typescript-client/build.jsr.sh index 205bb01297..c43f7d4fef 100755 --- a/typescript-client/build.jsr.sh +++ b/typescript-client/build.jsr.sh @@ -15,6 +15,6 @@ cp "${script_dirpath}/sqlUtils.ts" "${script_dirpath}/src/" echo "" >> "${script_dirpath}/src/index.ts" echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts" echo "" >> "${script_dirpath}/src/index.ts" -echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, signS3Objects, signS3Object, task, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable } from "./client";' >> "${script_dirpath}/src/index.ts" +echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, signS3Objects, signS3Object, task, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake } from "./client";' >> "${script_dirpath}/src/index.ts" diff --git a/typescript-client/build.sh b/typescript-client/build.sh index 35a4d2f8ee..4eba89f8c3 100755 --- a/typescript-client/build.sh +++ b/typescript-client/build.sh @@ -40,4 +40,4 @@ cp "${script_dirpath}/sqlUtils.ts" "${script_dirpath}/src/" echo "" >> "${script_dirpath}/src/index.ts" echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts" echo "" >> "${script_dirpath}/src/index.ts" -echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, signS3Objects, signS3Object, task, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable } from "./client";' >> "${script_dirpath}/src/index.ts" +echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, signS3Objects, signS3Object, task, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake } from "./client";' >> "${script_dirpath}/src/index.ts" diff --git a/typescript-client/client.d.ts b/typescript-client/client.d.ts index 41238ff787..b3d72b9e87 100644 --- a/typescript-client/client.d.ts +++ b/typescript-client/client.d.ts @@ -1,5 +1,20 @@ import { DenoS3LightClientSettings, type S3Object } from "./s3Types"; -export { AdminService, AuditService, FlowService, GranularAclService, GroupService, JobService, ResourceService, VariableService, ScriptService, ScheduleService, SettingsService, UserService, WorkspaceService, } from "./index"; +export { + AdminService, + AuditService, + FlowService, + GranularAclService, + GroupService, + JobService, + ResourceService, + VariableService, + ScriptService, + ScheduleService, + SettingsService, + UserService, + WorkspaceService, +} from "./index"; +export { datatable, ducklake } from "./sqlUtils"; export type Sql = string; export type Email = string; export type Base64 = string; @@ -17,19 +32,32 @@ export declare function getWorkspace(): string; * @param undefinedIfEmpty if the resource does not exist, return undefined instead of throwing an error * @returns resource value */ -export declare function getResource(path?: string, undefinedIfEmpty?: boolean): Promise; +export declare function getResource( + path?: string, + undefinedIfEmpty?: boolean +): Promise; /** * Get a resource value by path * @param jobId job id to get the root job id from (default to current job) * @returns root job id */ export declare function getRootJobId(jobId?: string): Promise; -export declare function runScript(path?: string | null, hash_?: string | null, args?: Record | null, verbose?: boolean): Promise; +export declare function runScript( + path?: string | null, + hash_?: string | null, + args?: Record | null, + verbose?: boolean +): Promise; export declare function waitJob(jobId: string, verbose?: boolean): Promise; export declare function getResult(jobId: string): Promise; export declare function getResultMaybe(jobId: string): Promise; export declare function task(f: (_: P) => T): (_: P) => Promise; -export declare function runScriptAsync(path: string | null, hash_: string | null, args: Record | null, scheduledInSeconds?: number | null): Promise; +export declare function runScriptAsync( + path: string | null, + hash_: string | null, + args: Record | null, + scheduledInSeconds?: number | null +): Promise; /** * Resolve a resource value in case the default value was picked because the input payload was undefined * @param obj resource value or path of the resource under the format `$res:path` @@ -43,7 +71,11 @@ export declare function getStatePath(): string; * @param value new value of the resource to set * @param initializeToTypeIfNotExist if the resource does not exist, initialize it with this type */ -export declare function setResource(value: any, path?: string, initializeToTypeIfNotExist?: string): Promise; +export declare function setResource( + value: any, + path?: string, + initializeToTypeIfNotExist?: string +): Promise; /** * Set the state * @param state state to set @@ -61,13 +93,20 @@ export declare function setState(state: any): Promise; * @param value value of the state */ -export declare function setFlowUserState(key: string, value: any, errorIfNotPossible?: boolean): Promise; +export declare function setFlowUserState( + key: string, + value: any, + errorIfNotPossible?: boolean +): Promise; /** * Get a flow user state * @param path path of the variable */ -export declare function getFlowUserState(key: string, errorIfNotPossible?: boolean): Promise; +export declare function getFlowUserState( + key: string, + errorIfNotPossible?: boolean +): Promise; /** * Get the internal state * @deprecated use getState instead @@ -90,9 +129,16 @@ export declare function getVariable(path: string): Promise; * @param isSecretIfNotExist if the variable does not exist, create it as secret or not (default: false) * @param descriptionIfNotExist if the variable does not exist, create it with this description (default: "") */ -export declare function setVariable(path: string, value: string, isSecretIfNotExist?: boolean, descriptionIfNotExist?: string): Promise; +export declare function setVariable( + path: string, + value: string, + isSecretIfNotExist?: boolean, + descriptionIfNotExist?: string +): Promise; export declare function databaseUrlFromResource(path: string): Promise; -export declare function denoS3LightClientSettings(s3_resource_path: string | undefined): Promise; +export declare function denoS3LightClientSettings( + s3_resource_path: string | undefined +): Promise; /** * Load the content of a file stored in S3. If the s3ResourcePath is undefined, it will default to the workspace S3 resource. * @@ -103,7 +149,10 @@ export declare function denoS3LightClientSettings(s3_resource_path: string | und * console.log(text); * ``` */ -export declare function loadS3File(s3object: S3Object, s3ResourcePath?: string | undefined): Promise; +export declare function loadS3File( + s3object: S3Object, + s3ResourcePath?: string | undefined +): Promise; /** * Load the content of a file stored in S3 as a stream. If the s3ResourcePath is undefined, it will default to the workspace S3 resource. * @@ -113,7 +162,10 @@ export declare function loadS3File(s3object: S3Object, s3ResourcePath?: string | * console.log(await fileContentBlob.text()); * ``` */ -export declare function loadS3FileStream(s3object: S3Object, s3ResourcePath?: string | undefined): Promise; +export declare function loadS3FileStream( + s3object: S3Object, + s3ResourcePath?: string | undefined +): Promise; /** * Persist a file to the S3 bucket. If the s3ResourcePath is undefined, it will default to the workspace S3 resource. * @@ -123,24 +175,28 @@ export declare function loadS3FileStream(s3object: S3Object, s3ResourcePath?: st * console.log(fileContentAsUtf8Str) * ``` */ -export declare function writeS3File(s3object: S3Object | undefined, fileContent: string | Blob, s3ResourcePath?: string | undefined): Promise; +export declare function writeS3File( + s3object: S3Object | undefined, + fileContent: string | Blob, + s3ResourcePath?: string | undefined +): Promise; /** * Get URLs needed for resuming a flow after this step * @param approver approver name * @returns approval page UI URL, resume and cancel API URLs for resuming the flow */ export declare function getResumeUrls(approver?: string): Promise<{ - approvalPage: string; - resume: string; - cancel: string; + approvalPage: string; + resume: string; + cancel: string; }>; /** * @deprecated use getResumeUrls instead */ export declare function getResumeEndpoints(approver?: string): Promise<{ - approvalPage: string; - resume: string; - cancel: string; + approvalPage: string; + resume: string; + cancel: string; }>; /** * Get an OIDC jwt token for auth to external services (e.g: Vault, AWS) (ee only) diff --git a/typescript-client/client.ts b/typescript-client/client.ts index 70e58671b9..7fae878b8c 100644 --- a/typescript-client/client.ts +++ b/typescript-client/client.ts @@ -17,7 +17,7 @@ import { type S3Object, } from "./s3Types"; -export { datatable } from "./sqlUtils"; +export { datatable, ducklake } from "./sqlUtils"; export { AdminService, diff --git a/typescript-client/sqlUtils.d.ts b/typescript-client/sqlUtils.d.ts index caf22aaef0..f84262415b 100644 --- a/typescript-client/sqlUtils.d.ts +++ b/typescript-client/sqlUtils.d.ts @@ -1,9 +1,8 @@ export type SqlStatement = { query(): Promise; }; - export interface SqlTemplateFunction { (strings: TemplateStringsArray, ...values: any[]): SqlStatement; } - export declare function datatable(name: string): SqlTemplateFunction; +export declare function ducklake(name: string): SqlTemplateFunction; diff --git a/typescript-client/sqlUtils.ts b/typescript-client/sqlUtils.ts index 5ea9d102e6..a1527a12e0 100644 --- a/typescript-client/sqlUtils.ts +++ b/typescript-client/sqlUtils.ts @@ -17,8 +17,7 @@ export interface SqlTemplateFunction { * let age = 21 * await sql` * SELECT * FROM friends - * WHERE name = ${name} - * AND age = ${age}::int + * WHERE name = ${name} AND age = ${age}::int * `.query() */ export function datatable(name: string = "main"): SqlTemplateFunction { @@ -43,11 +42,44 @@ export function datatable(name: string = "main"): SqlTemplateFunction { query: async () => { let result = await JobService.runScriptPreviewInline({ workspace: getWorkspace(), - requestBody: { - args, - content: queryStr, - language: "postgresql", - }, + requestBody: { args, content: queryStr, language: "postgresql" }, + }); + return result; + }, + } satisfies SqlStatement; + }; + return sql; +} + +/** + * @example + * let sql = wmill.ducklake() + * let name = 'Robin' + * let age = 21 + * await sql` + * SELECT * FROM friends + * WHERE name = ${name} AND age = ${age} + * `.query() + */ +export function ducklake(name: string = "main"): SqlTemplateFunction { + let sql: SqlTemplateFunction = ( + strings: TemplateStringsArray, + ...values: any[] + ) => { + let queryStr = values.map((_, i) => `-- $arg${i + 1}`).join("\n") + "\n"; + for (let i = 0; i < strings.length; i++) { + queryStr += strings[i]; + if (i !== strings.length - 1) queryStr += `$${i + 1}`; + } + queryStr += `ATTACH 'ducklake://${name}' AS dl;USE dl;\n`; + const args = Object.fromEntries(values.map((v, i) => [`arg${i + 1}`, v])); + return { + queryStr, + args, + query: async () => { + let result = await JobService.runScriptPreviewInline({ + workspace: getWorkspace(), + requestBody: { args, content: queryStr, language: "duckdb" }, }); return result; },