LCOV - code coverage report
Current view: top level - dashql-app/src/connections/catalog - catalog_query_pg_proc.ts Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 77 77
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as arrow from "apache-arrow";
       2              : 
       3              : import { QueryExecutor } from '../query_executor.js';
       4              : import { QueryExecutionArgs } from '../query_execution_args.js';
       5              : import { DynamicConnectionDispatch } from '../connection_registry.js';
       6              : import { CATALOG_UPDATE_REGISTER_QUERY } from "../connection_state.js";
       7              : import { QueryType } from '../../query/query_execution_state.js';
       8              : import { type FunctionMetadata, generateFunctionsSQL } from './catalog_function_sql_generator.js';
       9              : import { quoteIdentifier } from './catalog_sql_generator.js';
      10              : import type { LoggerLike } from '../../platform/logger/logger.js';
      11              : 
      12            1 : const CATALOG_QUERY_READ_TIMEOUT_MS = 60_000;
      13              : 
      14              : export type PgProcTable = arrow.Table<{
      15              :     function_schema: arrow.Utf8;
      16              :     function_name: arrow.Utf8;
      17              :     argument_types: arrow.List<arrow.Utf8>;
      18              :     argument_names: arrow.List<arrow.Utf8>;
      19              :     argument_modes: arrow.List<arrow.Utf8>;
      20              :     return_type: arrow.Utf8;
      21              :     returns_set: arrow.Bool;
      22              :     function_kind: arrow.Utf8;
      23              : }>;
      24              : 
      25            1 : export function generateCatalogSQLFromPgProc(result: PgProcTable, databaseName: string | null | undefined, logger?: LoggerLike): string {
      26            1 :     const functions: FunctionMetadata[] = [];
      27              : 
      28            1 :     for (const batch of result.batches) {
      29            1 :         const colSchema = batch.getChild("function_schema")!;
      30            1 :         const colName = batch.getChild("function_name")!;
      31            1 :         const colTypes = batch.getChild('argument_types')!;
      32            1 :         const colNames = batch.getChild('argument_names')!;
      33            1 :         const colModes = batch.getChild('argument_modes')!;
      34            1 :         const colReturnsSet = batch.getChild('returns_set')!;
      35            1 :         const colReturn = batch.getChild("return_type")!;
      36            1 :         const colKind = batch.getChild("function_kind")!;
      37              : 
      38            1 :         for (let i = 0; i < batch.numRows; ++i) {
      39            1 :             const schemaName = colSchema.at(i);
      40            1 :             const functionName = colName.at(i);
      41            1 :             const returnType = colReturn.at(i);
      42            1 :             const kind = colKind.at(i);
      43              : 
      44            1 :             if (!functionName) {
      45            1 :                 continue;
      46              :             }
      47            1 :             const types = Array.from(colTypes.at(i) ?? []);
      48            1 :             const names = Array.from(colNames.at(i) ?? []);
      49            1 :             const modes = Array.from(colModes.at(i) ?? []);
      50            1 :             if (!types.every(type => typeof type === 'string')
      51            1 :                 || !names.every(name => name == null || typeof name === 'string')
      52            1 :                 || !modes.every(mode => typeof mode === 'string')
      53            1 :                 || (names.length !== 0 && names.length !== types.length)
      54            1 :                 || (modes.length !== 0 && modes.length !== types.length)) {
      55            1 :                 throw new Error('pg_proc returned invalid function argument metadata');
      56              :             }
      57              :             // DashQL declarations model scalar signatures only. Qualified custom types and
      58              :             // set/table results are not in its type grammar; do not let one poison the batch.
      59            1 :             const unsupportedType = (type: string) => type.replace(/"(?:[^"]|"")*"/g, '').includes('.');
      60            1 :             if (colReturnsSet.at(i) || modes.includes('t') || !returnType
      61            1 :                 || unsupportedType(returnType) || types.some(unsupportedType)
      62            1 :                 || modes.some(mode => !['i', 'o', 'b', 'v', 't'].includes(mode))) {
      63            1 :                 logger?.warn('Skipping unsupported PostgreSQL function signature', {
      64            1 :                     schema: schemaName, function: functionName, returnType,
      65            1 :                 }, 'catalog_pg');
      66            1 :                 continue;
      67            1 :             }
      68            1 :             const args = types.flatMap((type, index) => modes[index] === 'o' ? [] : [
      69            1 :                 `${quoteIdentifier(names[index] || `arg${index + 1}`)} ${type}`,
      70            1 :             ]).join(', ');
      71            1 :             functions.push({
      72            1 :                 schemaName,
      73            1 :                 functionName,
      74            1 :                 arguments: args,
      75            1 :                 returnType,
      76            1 :                 isAggregate: kind === 'a',
      77            1 :             });
      78            1 :         }
      79            1 :     }
      80              : 
      81            1 :     return generateFunctionsSQL(databaseName, functions);
      82            1 : }
      83              : 
      84            1 : export async function queryPgProc(
      85            1 :     connectionId: string,
      86            1 :     connectionDispatch: DynamicConnectionDispatch,
      87            1 :     updateId: number,
      88            1 :     executor: QueryExecutor,
      89            1 :     abortSignal?: AbortSignal,
      90            1 : ): Promise<PgProcTable | null> {
      91              :     const query = `
      92              :         SELECT
      93              :             n.nspname AS function_schema,
      94              :             p.proname AS function_name,
      95              :             -- Hyper cannot cast oidvector to oid[]; format each source separately.
      96              :             CASE WHEN p.proallargtypes IS NOT NULL THEN ARRAY(
      97              :                 SELECT pg_catalog.format_type(arg.type_oid, NULL)
      98              :                 FROM unnest(p.proallargtypes) WITH ORDINALITY AS arg(type_oid, position)
      99              :                 ORDER BY arg.position
     100              :             ) ELSE ARRAY(
     101              :                 SELECT pg_catalog.format_type(arg.type_oid, NULL)
     102              :                 FROM unnest(p.proargtypes) WITH ORDINALITY AS arg(type_oid, position)
     103              :                 ORDER BY arg.position
     104              :             ) END AS argument_types,
     105              :             p.proargnames AS argument_names,
     106              :             p.proargmodes AS argument_modes,
     107              :             COALESCE(pg_catalog.format_type(p.prorettype, NULL), 'any') AS return_type,
     108              :             p.proretset AS returns_set,
     109              :             p.prokind AS function_kind
     110              :         FROM pg_proc p
     111              :         JOIN pg_namespace n ON n.oid = p.pronamespace
     112              :         WHERE p.prokind IN ('f', 'a', 'w', 'p')
     113              :         ORDER BY n.nspname, p.proname
     114              :     `;
     115              : 
     116              :     const args: QueryExecutionArgs = {
     117            1 :         query: query,
     118            1 :         abortSignal,
     119            1 :         readTimeoutMs: CATALOG_QUERY_READ_TIMEOUT_MS,
     120            1 :         throwOnError: true,
     121            1 :         metadata: {
     122            1 :             queryType: QueryType.CATALOG_QUERY_PG_PROC,
     123            1 :             title: "Query Postgres Functions",
     124            1 :             description: null,
     125            1 :             issuer: "Catalog Update",
     126            1 :             userProvided: false
     127            1 :         }
     128              :     };
     129            1 :     const [queryId, queryExecution] = executor(connectionId, args);
     130            1 :     connectionDispatch(connectionId, {
     131            1 :         type: CATALOG_UPDATE_REGISTER_QUERY,
     132            1 :         value: [updateId, queryId]
     133            1 :     });
     134              : 
     135            1 :     const queryResult = await queryExecution as PgProcTable;
     136            1 :     return queryResult;
     137            1 : }
        

Generated by: LCOV version 2.0-1