mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
9bbab3321e
* data tables settings ui * install runed * zod 4 fixes * use new toJSONSchema * Migrate ducklake catalogs to more generic custom instance databases * fix compilation * Safety conversion for old duckdb ffi * data tables settings * ts client basis * inline run works * datatables work * Revert "datatables work" This reverts commit6e1588d59e. * datatables work (without leaking pg credentials) * println * separate sqlUtils.ts * nit * Separate custom instance db Select and Wizard components * nit * nit wording * add tags to custom instance dbs * error when trying to use ducklake as datatable or opposite * show status in dropdown * data table instance setup works * sqk function for ducklake * factorize logic * fix temp reactivity * Data table assetexplore * Migrate S3 permissions to modal * Revert "Migrate S3 permissions to modal" This reverts commit0631d03cb0. * nit query -> fetch * Custom instance setup new look * run_language_executor separate fn * run_inline param * nit wording * Better typed client * Data tables display as assets in frontend * asset db icon * nit * cleaner errors * nit * Fix sed calls in mac * run_inline_script_preview in python client * basic python datatable client * datatable and datalake parser in python * ducklake client python * nit fix * Fix migration producing NULL instead of {} when no custom databases * merge conflict fail * python ducklake client arg fix * parse or infer sql types in ts client * ts asset parser, detect datatable & ducklake R/W * fix sql repl for other read ops than select * export type SqlTemplateFunction * rename list_custom_instance_pg_databases * typecheck datatable and ducklake name in Typescript * Fix typecheck datatable and ducklake in TS * declare module overriding instead of extending * infer_sql_type in python client * SqlQuery object in python * fix merge conflicts * update const_format * CI fix * factor out to var_identifiers * sqlx prepare * unnecessary security (admin is required) * clearer comment * ee repo ref * nit snake case * claude step 1: detect var declarations * move detect_sql_access_type to common mod * claude step 2: detect when saved vars are queried * Revert "claude step 2: detect when saved vars are queried" This reverts commit1e1f930568. * Revert "claude step 1: detect var declarations" This reverts commitf866f4819d. * remove ducklake/datatable and default * detect data table assigns in var_identifiers * Python parser successfully infers R/W/RW from ducklake / datatable * still register ducklake/datatable if not used as unknown R/W * Go to settings button in Assets Dropdown on not found * nit * sqlx prepare fail * manual fix, somehow sqlx prepare won't do it * fix frontend ci * ee repo ref * ducklake_user doesnt exist in unit tests * nit fix * ui nit * nit * nit missing clone * fork ducklakes and datatables * fix surface hover bug * stupid mistake * better deeply reactive mutable derived * Ducklake picker * Editor bar data tables * DuckDB supports datatables * datatable in duckdb asset parser * duckdb asset parser var_identifiers * Revert "duckdb asset parser var_identifiers" This reverts commit88068b1a77. * sqlx prepare * Box pin in test_workflow_as_code to fix stack overflow * go to settings button * ee repo ref * fix compilation * wording nit
219 lines
6.7 KiB
TypeScript
219 lines
6.7 KiB
TypeScript
import { getWorkspace, JobService } from "./client";
|
|
|
|
type ResultCollection =
|
|
| "last_statement_all_rows"
|
|
| "last_statement_first_row"
|
|
| "last_statement_all_rows_scalar"
|
|
| "last_statement_first_row_scalar"
|
|
| "all_statements_all_rows"
|
|
| "all_statements_first_row"
|
|
| "all_statements_all_rows_scalar"
|
|
| "all_statements_first_row_scalar"
|
|
| "legacy";
|
|
|
|
type FetchParams<ResultCollectionT extends ResultCollection> = {
|
|
resultCollection?: ResultCollectionT;
|
|
};
|
|
|
|
type SqlResult<ResultCollectionT extends ResultCollection> =
|
|
ResultCollectionT extends "last_statement_first_row"
|
|
? object
|
|
: ResultCollectionT extends "all_statements_first_row"
|
|
? object[]
|
|
: ResultCollectionT extends "last_statement_all_rows"
|
|
? object[]
|
|
: ResultCollectionT extends "all_statements_all_rows"
|
|
? object[][]
|
|
: ResultCollectionT extends "last_statement_all_rows_scalar"
|
|
? any[]
|
|
: ResultCollectionT extends "all_statements_all_rows_scalar"
|
|
? any[][]
|
|
: ResultCollectionT extends "last_statement_first_row_scalar"
|
|
? any
|
|
: ResultCollectionT extends "all_statements_first_row_scalar"
|
|
? any[]
|
|
: unknown;
|
|
export type SqlStatement = {
|
|
content: string;
|
|
|
|
args: Record<string, any>;
|
|
|
|
fetch<ResultCollectionT extends ResultCollection = "last_statement_all_rows">(
|
|
params?: FetchParams<ResultCollectionT | ResultCollection> // The union is for auto-completion
|
|
): Promise<SqlResult<ResultCollectionT>>;
|
|
|
|
fetchOne(
|
|
params?: Omit<FetchParams<"last_statement_first_row">, "resultCollection">
|
|
): Promise<SqlResult<"last_statement_first_row">>;
|
|
};
|
|
|
|
export interface SqlTemplateFunction {
|
|
(strings: TemplateStringsArray, ...values: any[]): SqlStatement;
|
|
}
|
|
|
|
/**
|
|
* @example
|
|
* let sql = wmill.datatable()
|
|
* let name = 'Robin'
|
|
* let age = 21
|
|
* await sql`
|
|
* SELECT * FROM friends
|
|
* WHERE name = ${name} AND age = ${age}::int
|
|
* `.fetch()
|
|
*/
|
|
export function datatable(name: string = "main"): SqlTemplateFunction {
|
|
return sqlProviderImpl(name, "datatable");
|
|
}
|
|
|
|
/**
|
|
* @example
|
|
* let sql = wmill.ducklake()
|
|
* let name = 'Robin'
|
|
* let age = 21
|
|
* await sql`
|
|
* SELECT * FROM friends
|
|
* WHERE name = ${name} AND age = ${age}
|
|
* `.fetch()
|
|
*/
|
|
export function ducklake(name: string = "main"): SqlTemplateFunction {
|
|
return sqlProviderImpl(name, "ducklake");
|
|
}
|
|
|
|
function sqlProviderImpl(
|
|
name: string,
|
|
provider: "datatable" | "ducklake"
|
|
): SqlTemplateFunction {
|
|
let sql: SqlTemplateFunction = (
|
|
strings: TemplateStringsArray,
|
|
...values: any[]
|
|
) => {
|
|
let formatArgDecl = {
|
|
datatable: (i: number) => `-- $${i + 1} arg${i + 1}`,
|
|
ducklake: (i: number) => {
|
|
let argType =
|
|
parseTypeAnnotation(strings[i], strings[i + 1]) ||
|
|
inferSqlType(values[i]);
|
|
return `-- $arg${i + 1} (${argType})`;
|
|
},
|
|
}[provider];
|
|
|
|
let formatArgUsage = {
|
|
datatable: (i: number) => {
|
|
let argType =
|
|
parseTypeAnnotation(strings[i], strings[i + 1]) ||
|
|
inferSqlType(values[i]);
|
|
return `$${i + 1}::${argType}`;
|
|
},
|
|
ducklake: (i: number) => `$arg${i + 1}`,
|
|
}[provider];
|
|
|
|
let content = values.map((_, i) => formatArgDecl(i)).join("\n") + "\n";
|
|
if (provider === "ducklake")
|
|
content += `ATTACH 'ducklake://${name}' AS dl;USE dl;\n`;
|
|
|
|
let contentBody = "";
|
|
for (let i = 0; i < strings.length; i++) {
|
|
contentBody += strings[i];
|
|
if (i !== strings.length - 1) contentBody += formatArgUsage(i);
|
|
}
|
|
content += contentBody;
|
|
|
|
const args = {
|
|
...Object.fromEntries(values.map((v, i) => [`arg${i + 1}`, v])),
|
|
...(provider === "datatable" ? { database: `datatable://${name}` } : {}),
|
|
};
|
|
const language = {
|
|
datatable: "postgresql" as const,
|
|
ducklake: "duckdb" as const,
|
|
}[provider];
|
|
|
|
async function fetch<ResultCollectionT extends ResultCollection>({
|
|
resultCollection,
|
|
}: FetchParams<ResultCollectionT> = {}) {
|
|
if (resultCollection)
|
|
content = `-- result_collection=${resultCollection}\n${content}`;
|
|
try {
|
|
let result = await JobService.runScriptPreviewInline({
|
|
workspace: getWorkspace(),
|
|
requestBody: { args, content, language },
|
|
});
|
|
return result as SqlResult<ResultCollectionT>;
|
|
} catch (e: any) {
|
|
let err = e;
|
|
if (
|
|
e &&
|
|
typeof e.body == "string" &&
|
|
e.statusText == "Internal Server Error"
|
|
) {
|
|
let body = e.body;
|
|
if (body.startsWith("Internal:")) body = body.slice(9).trim();
|
|
if (body.startsWith("Error:")) body = body.slice(6).trim();
|
|
if (body.startsWith("datatable")) body = body.slice(9).trim();
|
|
err = Error(`${provider} ${body}`);
|
|
err.query = contentBody;
|
|
err.request = e.request;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
return {
|
|
content,
|
|
args,
|
|
fetch,
|
|
fetchOne: (params) =>
|
|
fetch({ ...params, resultCollection: "last_statement_first_row" }),
|
|
} satisfies SqlStatement;
|
|
};
|
|
return sql;
|
|
}
|
|
|
|
// DuckDB executor requires explicit argument types at declaration
|
|
// And postgres at argument usage.
|
|
// These types exist in both DuckDB and Postgres
|
|
// Check that the types exist if you plan to extend this function for other SQL engines.
|
|
function inferSqlType(value: any): string {
|
|
if (typeof value === "number" || typeof value === "bigint") {
|
|
if (Number.isInteger(value)) return "BIGINT";
|
|
return "DOUBLE PRECISION";
|
|
} else if (value === null || value === undefined) {
|
|
return "TEXT";
|
|
} else if (typeof value === "string") {
|
|
return "TEXT";
|
|
} else if (typeof value === "object") {
|
|
return "JSON";
|
|
} else if (typeof value === "boolean") {
|
|
return "BOOLEAN";
|
|
} else {
|
|
return "TEXT";
|
|
}
|
|
}
|
|
|
|
// The goal is to detect if the user added a type annotation manually
|
|
//
|
|
// untyped : sql`SELECT ${x} = 0` => ['SELECT ', ' = 0']
|
|
// typed : sql`SELECT ${x}::int = 0` => ['SELECT ', '::int = 0']
|
|
// typed : sql`SELECT CAST ( ${x} AS int ) = 0` => ['SELECT CAST ( ', ' AS int ) = 0']
|
|
function parseTypeAnnotation(
|
|
prevTemplateString: string | undefined,
|
|
nextTemplateString: string | undefined
|
|
): string | undefined {
|
|
if (!nextTemplateString) return;
|
|
nextTemplateString = nextTemplateString.trimStart();
|
|
if (nextTemplateString.startsWith("::")) {
|
|
return nextTemplateString.substring(2).trimStart().split(/\s+/)[0];
|
|
}
|
|
prevTemplateString = prevTemplateString?.trimEnd();
|
|
if (
|
|
prevTemplateString?.endsWith("(") &&
|
|
prevTemplateString
|
|
.substring(0, prevTemplateString.length - 1)
|
|
.trim()
|
|
.toUpperCase()
|
|
.endsWith("CAST") &&
|
|
nextTemplateString.toUpperCase().startsWith("AS ")
|
|
) {
|
|
return nextTemplateString.substring(2).trimStart().split(/\s+/)[0];
|
|
}
|
|
}
|