mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 08:01:25 +00:00
28c8b5c60f
* feat(cli): add datatable list and run commands * feat(cli): render datatable query results as a table * feat(cli): serve datatables as a postgres-wire endpoint * feat(cli): add 'datatable psql' to launch psql against the proxy * feat(cli): route datatable serve by client-supplied database name * override database list + password option * fix: support extended queries in datatable serve * fix: correct cloud size threshold log and parse CLI descriptions with parens/trailing comma * refactor: extract raw_output envelope encoding into pg_raw_output module --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
76 lines
2.9 KiB
TypeScript
76 lines
2.9 KiB
TypeScript
const CURRENT_DATABASE_QUERY_RE = /\b(?:pg_catalog\.)?current_database\s*\(\s*\)/i;
|
|
const CURRENT_DATABASE_RE = /\b(?:pg_catalog\.)?current_database\s*\(\s*\)/gi;
|
|
const PG_DATABASE_RE = /\b(?:pg_catalog\.)?pg_database\b/i;
|
|
const PG_DATABASE_FROM_OR_JOIN_RE =
|
|
/(\b(?:from|join)\s+(?:only\s+)?)(?:pg_catalog\.)?pg_database\b/gi;
|
|
const PG_DATABASE_COMMA_JOIN_RE = /(,\s*)(?:pg_catalog\.)?pg_database\b/gi;
|
|
const PG_SIZE_PRETTY_DATABASE_SIZE_RE =
|
|
/\b(?:pg_catalog\.)?pg_size_pretty\s*\(\s*(?:pg_catalog\.)?pg_database_size\s*\((?:[^()]|\([^()]*\))*\)\s*\)/gi;
|
|
const PG_DATABASE_SIZE_RE =
|
|
/\b(?:pg_catalog\.)?pg_database_size\s*\((?:[^()]|\([^()]*\))*\)/gi;
|
|
const HAS_DATABASE_PRIVILEGE_RE =
|
|
/\b(?:pg_catalog\.)?has_database_privilege\s*\((?:[^()]|\([^()]*\))*\)/gi;
|
|
const PG_DATABASE_COLLATION_VERSION_RE =
|
|
/\b(?:pg_catalog\.)?pg_database_collation_actual_version\s*\((?:[^()]|\([^()]*\))*\)/gi;
|
|
|
|
export function queryTouchesVirtualDatabaseCatalog(query: string): boolean {
|
|
return CURRENT_DATABASE_QUERY_RE.test(query) || PG_DATABASE_RE.test(query);
|
|
}
|
|
|
|
export function virtualizeDatabaseCatalogQuery(
|
|
query: string,
|
|
currentDatabaseName: string,
|
|
databaseNames: string[],
|
|
): string {
|
|
let rewritten = query.replace(
|
|
CURRENT_DATABASE_RE,
|
|
sqlStringLiteral(currentDatabaseName),
|
|
);
|
|
|
|
if (!PG_DATABASE_RE.test(query)) {
|
|
return rewritten;
|
|
}
|
|
|
|
const relation = buildVirtualPgDatabaseRelation(databaseNames);
|
|
rewritten = rewritten
|
|
.replace(PG_SIZE_PRETTY_DATABASE_SIZE_RE, "NULL::text")
|
|
.replace(PG_DATABASE_SIZE_RE, "NULL::bigint")
|
|
.replace(HAS_DATABASE_PRIVILEGE_RE, "true")
|
|
.replace(PG_DATABASE_COLLATION_VERSION_RE, "NULL::text")
|
|
.replace(PG_DATABASE_FROM_OR_JOIN_RE, `$1${relation}`)
|
|
.replace(PG_DATABASE_COMMA_JOIN_RE, `$1${relation}`);
|
|
|
|
return rewritten;
|
|
}
|
|
|
|
function buildVirtualPgDatabaseRelation(databaseNames: string[]): string {
|
|
const names = Array.from(new Set(databaseNames));
|
|
const arrayLiteral = names.map(sqlStringLiteral).join(", ");
|
|
|
|
return `(
|
|
SELECT
|
|
(20000 + src.ordinality)::oid AS oid,
|
|
src.datname,
|
|
COALESCE((SELECT oid FROM pg_catalog.pg_roles WHERE rolname = CURRENT_USER LIMIT 1), 10::oid) AS datdba,
|
|
pg_char_to_encoding(current_setting('server_encoding')) AS encoding,
|
|
'c'::"char" AS datlocprovider,
|
|
false AS datistemplate,
|
|
true AS datallowconn,
|
|
-1 AS datconnlimit,
|
|
'0'::xid AS datfrozenxid,
|
|
'1'::xid AS datminmxid,
|
|
COALESCE((SELECT oid FROM pg_catalog.pg_tablespace WHERE spcname = 'pg_default' LIMIT 1), 1663::oid) AS dattablespace,
|
|
current_setting('lc_collate') AS datcollate,
|
|
current_setting('lc_ctype') AS datctype,
|
|
NULL::text AS daticulocale,
|
|
NULL::text AS daticurules,
|
|
NULL::text AS datcollversion,
|
|
NULL::aclitem[] AS datacl
|
|
FROM unnest(ARRAY[${arrayLiteral}]::text[]) WITH ORDINALITY AS src(datname, ordinality)
|
|
)`;
|
|
}
|
|
|
|
function sqlStringLiteral(value: string): string {
|
|
return `'${value.replaceAll("'", "''")}'`;
|
|
}
|