Line data Source code
1 : import type * as arrow from 'apache-arrow';
2 :
3 : import type { QueryExecutor } from '../../connections/query_executor.js';
4 : import { SessionScripts, ScriptData, isAnalysisOutdated } from './session_scripts.js';
5 : import { ensureSessionScriptAnalyzed, ModifySessionScripts } from './session_scripts_registry.js';
6 : import { stringifyError, type LoggerLike } from '../../platform/logger/logger.js';
7 : import { isSessionExecutionRunning, releaseSessionExecution, waitForSessionExecution } from './session_execution.js';
8 : import { runPlannedScript, scriptRunServices, type ScriptRunOptions } from './script_run.js';
9 :
10 : /// Run a script on its connection, resolves when the run ended. A failed statement and a plan that fails, e.g. with an
11 : /// unknown script, show their errors in the script. A refresh reads no cached results, neither of the script nor of
12 : /// the scripts it references.
13 1 : export async function runSessionScript(
14 1 : sessionScripts: SessionScripts,
15 1 : scriptData: ScriptData,
16 1 : executeQuery: QueryExecutor,
17 1 : modifySessionScripts: ModifySessionScripts,
18 1 : logger: LoggerLike,
19 1 : options: ScriptRunOptions = {},
20 1 : ): Promise<void> {
21 1 : if (isSessionExecutionRunning(sessionScripts)) return;
22 1 : logger?.info('Session script execution requested', {
23 1 : connectionId: scriptData.connection.connectionId,
24 1 : sessionId: sessionScripts.sessionId,
25 1 : scriptKey: scriptData.scriptKey.toString(),
26 1 : analysisOutdated: scriptData.analysisOutdated.toString(),
27 1 : analysisAvailable: scriptData.editorUpdate?.analysisAvailable.toString(),
28 1 : textVersion: scriptData.editorUpdate?.textVersion.toString(),
29 1 : nativeTextVersion: scriptData.scriptSession.getTextVersion?.().toString(),
30 1 : textLength: scriptData.scriptSession.getText?.().length.toString(),
31 1 : }, 'session_execution');
32 1 : if (isAnalysisOutdated(scriptData)) {
33 1 : const analyzed = await ensureSessionScriptAnalyzed(sessionScripts, scriptData.scriptKey, modifySessionScripts);
34 1 : if (analyzed == null) {
35 0 : logger?.warn('Session execution stopped because analysis returned no script', {
36 0 : sessionId: sessionScripts.sessionId,
37 0 : scriptKey: scriptData.scriptKey.toString(),
38 0 : }, 'session_execution');
39 0 : return;
40 0 : }
41 1 : logger?.info('Outdated session script analysis completed', {
42 1 : sessionId: sessionScripts.sessionId,
43 1 : scriptKey: scriptData.scriptKey.toString(),
44 1 : analysisAvailable: analyzed.editorUpdate?.analysisAvailable.toString(),
45 1 : }, 'session_execution');
46 1 : scriptData = analyzed;
47 1 : }
48 1 : await executeSessionScript(scriptData, sessionScripts, executeQuery, modifySessionScripts, logger, false, options);
49 1 : }
50 :
51 : /// Run a script on its connection and wait for its result, e.g. a run of the agent. The run waits for the run of
52 : /// another script of the session to end. Fails when the whole session runs, the script is gone, can't be planned,
53 : /// holds no statement, or a statement fails or is cancelled. `beforeRun` sees the script as the run gets it, analyzed
54 : /// and after the wait, and refuses the run by throwing.
55 1 : export async function runSessionScriptForResult(
56 1 : getSession: () => SessionScripts | null,
57 1 : scriptKey: number,
58 1 : executeQuery: QueryExecutor,
59 1 : modifySessionScripts: ModifySessionScripts,
60 1 : logger: LoggerLike,
61 1 : beforeRun?: (scriptData: ScriptData) => void,
62 1 : ): Promise<arrow.Table | null> {
63 1 : const sessionId = getSession()?.sessionId;
64 1 : if (sessionId == null) throw new Error('The session is gone');
65 1 : await waitForSessionExecution(sessionId);
66 1 : let handedOver = false;
67 1 : try {
68 : // The session as the runs before left it, e.g. with analyzed scripts
69 1 : const waited = getSession();
70 1 : const scriptData = waited?.scripts[scriptKey];
71 1 : if (waited == null || scriptData == null) throw new Error('The script is gone');
72 1 : const analyzed = isAnalysisOutdated(scriptData)
73 1 : ? await ensureSessionScriptAnalyzed(waited, scriptKey, modifySessionScripts)
74 1 : : scriptData;
75 1 : if (analyzed == null) {
76 0 : throw new Error('The script could not be analyzed');
77 : }
78 1 : beforeRun?.(analyzed);
79 : // The run releases the session. The caller reports the failure, e.g. to the agent, the script shows it as well.
80 1 : handedOver = true;
81 1 : return await executeSessionScript(analyzed, getSession() ?? waited, executeQuery, modifySessionScripts, logger, true, { held: true });
82 1 : } finally {
83 1 : if (!handedOver) releaseSessionExecution(sessionId);
84 1 : }
85 1 : }
86 :
87 : /// Run a script through a plan of the script and the scripts it references (docs/design/script_references.md)
88 1 : function executeSessionScript(
89 1 : scriptData: ScriptData,
90 1 : sessionScripts: SessionScripts,
91 1 : executeQuery: QueryExecutor,
92 1 : modifySessionScripts: ModifySessionScripts,
93 1 : logger: LoggerLike,
94 1 : throwOnError: boolean = false,
95 1 : options: ScriptRunOptions = {},
96 1 : ): Promise<arrow.Table | null> {
97 1 : const services = scriptRunServices(sessionScripts.instance);
98 1 : return runPlannedScript(services, sessionScripts, scriptData, executeQuery, modifySessionScripts, { logger, ...options }).catch(error => {
99 1 : if (throwOnError) throw error;
100 1 : logger?.warn('Session script run failed', {
101 1 : sessionId: sessionScripts.sessionId,
102 1 : scriptKey: scriptData.scriptKey.toString(),
103 1 : error: stringifyError(error),
104 1 : }, 'session_execution');
105 1 : return null;
106 1 : });
107 1 : }
|