LCOV - code coverage report
Current view: top level - dashql-app/src/session/shell - session_shell_environment.ts Coverage Total Hit
Test: DashQL d805a4b Lines: 93.8 % 64 60
Test Date: 2026-11-09 20:22:13

            Line data    Source code
       1              : import * as dashql from '@ankoh/dashql-core';
       2              : 
       3              : import type { ClientFunctionCallDescription } from '../../connections/clfn/clfn_spec.js';
       4              : import { CancelQuery, QueryExecutor } from '../../connections/query_executor.js';
       5              : import type { DashQLShellEnvironment } from '../../shell/api.js';
       6              : import { reportShellTiming, SHELL_QUERY_METADATA } from '../../shell/shell_result.js';
       7              : 
       8              : /// The client function call of a shell statement, null if the statement goes to the backend. A spec shadows its
       9              : /// namespace and no backend judges a call in it, so a call that can't run fails here, like it fails a session plan.
      10            1 : export function resolveClientFunctionCall(catalog: dashql.DashQLCatalog, query: string): ClientFunctionCallDescription | null {
      11            1 :     const script = catalog.ptr.api.createScript(catalog);
      12            1 :     try {
      13            1 :         script.replaceText(query);
      14            1 :         script.analyze();
      15            1 :         if (!script.startsClientFunctionCall()) return null;
      16            1 :         const parsed = script.getParsed().read();
      17            1 :         const syntaxError = parsed.scannerErrorsLength() > 0 ? parsed.scannerErrors(0) : parsed.parserErrorsLength() > 0 ? parsed.parserErrors(0) : null;
      18            1 :         if (syntaxError != null) throw new Error(syntaxError.message() ?? 'Invalid client function call');
      19            1 :         if (parsed.statementsLength() > 1) throw new Error('A client function call runs as a statement of its own');
      20            1 :         const analyzed = script.getAnalyzed().read();
      21            1 :         for (let i = 0; i < analyzed.errorsLength(); ++i) {
      22            1 :             const error = analyzed.errors(i)!;
      23            1 :             if (error.severity() === dashql.buffers.analyzer.AnalyzerErrorSeverity.ERROR) throw new Error(error.message() ?? 'Invalid client function call');
      24            1 :         }
      25            1 :         const description = JSON.parse(analyzed.clientFunctionCalls(0)!.description()!) as ClientFunctionCallDescription;
      26              :         // The inputs of a session belong to its scripts, the shell has no values for them
      27            1 :         const binding = description.bindings?.[0];
      28            1 :         if (binding != null) throw new Error(`input :${binding.param} has no value`);
      29            1 :         return description;
      30            1 :     } finally {
      31            1 :         script.destroy();
      32            1 :     }
      33            1 : }
      34              : 
      35            1 : export function createSessionShellEnvironment(
      36            1 :     sessionId: string,
      37            1 :     connectionId: string,
      38            1 :     executeQuery: QueryExecutor,
      39            1 :     cancelQuery: CancelQuery,
      40            1 :     releaseResult: (queryId: number) => void = () => { },
      41            1 :     resolveCall: (query: string) => ClientFunctionCallDescription | null = () => null,
      42            1 : ): DashQLShellEnvironment {
      43            1 :     return {
      44            1 :         async executeQuery(query, signal, onProgress, onResult, onTiming) {
      45            1 :             const reportTiming = (label: string, elapsedMs: number) => reportShellTiming(onTiming, label, elapsedMs);
      46            1 :             const clientFunctionCall = resolveCall(query) ?? undefined;
      47            1 :             const [queryId, execution] = executeQuery(connectionId, {
      48            1 :                 query,
      49            1 :                 clientFunctionCall,
      50            1 :                 analyzeResults: true,
      51            1 :                 cacheable: false,
      52            1 :                 throwOnError: true,
      53            1 :                 onLog: onProgress,
      54            1 :                 onTiming: reportTiming,
      55            1 :                 metadata: { ...SHELL_QUERY_METADATA, sessionId },
      56            1 :             });
      57            1 :             const abort = () => cancelQuery(connectionId, queryId);
      58            1 :             if (signal?.aborted) {
      59            0 :                 abort();
      60              :             } else {
      61            1 :                 signal?.addEventListener('abort', abort, { once: true });
      62              :             }
      63            1 :             try {
      64            1 :                 const table = await execution;
      65            1 :                 if (table == null) {
      66            0 :                     if (signal?.aborted) throw new DOMException('Query was cancelled', 'AbortError');
      67            0 :                     throw new Error('Query failed without an error');
      68            0 :                 }
      69            1 :                 if (table.numCols > 0) {
      70            1 :                     onResult?.(queryId, table.numRows);
      71              :                 } else {
      72              :                     // A statement without columns has nothing to show, its computation is not needed
      73            1 :                     releaseResult(queryId);
      74              :                 }
      75            1 :             } finally {
      76            1 :                 signal?.removeEventListener('abort', abort);
      77            1 :             }
      78            1 :         },
      79            1 :     };
      80            1 : }
        

Generated by: LCOV version 2.0-1