Line data Source code
1 : import * as core from '@ankoh/dashql-core';
2 :
3 : import { sessionPlanRequest } from '../inputs/session_inputs.js';
4 : import { orderedSessionScripts, type SessionScripts } from './session_scripts.js';
5 :
6 1 : const NONE = 0xFFFFFFFF;
7 :
8 : /// What a script reference reads
9 1 : enum ScriptReferenceKind {
10 : /// The query of the referenced script is inlined into the statement, both run on one connection
11 1 : Inline = 'inline',
12 : /// The statement scans the result of a script of another connection, materialized as Parquet file
13 1 : Scan = 'scan',
14 : /// The reference has errors
15 1 : Invalid = 'invalid',
16 : /// The statement scans an attachment of the session
17 1 : Attachment = 'attachment',
18 1 : }
19 :
20 : /// A checked table parameter of a script, `:foo`
21 : interface ScriptReferenceMark {
22 : /// The begin of the parameter, at the prefix, in UTF-16 code units
23 : from: number;
24 : /// The end of the parameter
25 : to: number;
26 : /// The parameter name
27 : parameter: string;
28 : /// The file name of the referenced script, null if the name finds none
29 : target: string | null;
30 : /// The file name of the referenced attachment, null if the name finds none
31 : attachment: string | null;
32 : /// What the reference reads
33 : kind: ScriptReferenceKind;
34 : /// The errors of the reference
35 : errors: string[];
36 : }
37 :
38 : /// The checked script references of a script
39 : export interface ScriptReferenceReport {
40 : /// The text version of the analysis that was checked, the marks lie in that text
41 : textVersion: bigint;
42 : /// The table parameters in text order
43 : marks: ScriptReferenceMark[];
44 : }
45 :
46 : /// Check the script references of every script of a session, by script key. A script whose analysis is not current
47 : /// with its text has no report.
48 1 : export function computeScriptReferenceReports(session: SessionScripts): Map<number, ScriptReferenceReport> {
49 1 : const scripts = orderedSessionScripts(session);
50 1 : const reports = new Map<number, ScriptReferenceReport>();
51 1 : if (scripts.length === 0) return reports;
52 1 : const request = sessionPlanRequest(scripts, scripts.map(script => script.fileName), session);
53 1 : const check: core.buffers.execution.ScriptReferenceCheckT = session.instance.checkScriptReferences(request).unpack();
54 1 : check.scripts.forEach((checked, index) => {
55 1 : if (!checked.checked) return;
56 1 : const marks = checked.references.map(reference => {
57 1 : const span = reference.textSpan;
58 1 : const from = span?.offset ?? 0;
59 1 : let kind: ScriptReferenceKind;
60 1 : if (reference.errors.length > 0 || reference.kind === core.buffers.execution.ScriptReferenceKind.INVALID) {
61 1 : kind = ScriptReferenceKind.Invalid;
62 1 : } else if (reference.kind === core.buffers.execution.ScriptReferenceKind.SCAN) {
63 0 : kind = ScriptReferenceKind.Scan;
64 1 : } else if (reference.kind === core.buffers.execution.ScriptReferenceKind.ATTACHMENT) {
65 0 : kind = ScriptReferenceKind.Attachment;
66 : } else {
67 1 : kind = ScriptReferenceKind.Inline;
68 : }
69 1 : return {
70 1 : from,
71 1 : to: from + (span?.length ?? 0),
72 1 : parameter: String(reference.parameter ?? ''),
73 1 : target: reference.script !== NONE ? scripts[reference.script]?.fileName ?? null : null,
74 1 : attachment: reference.attachment !== NONE ? request.attachments?.[reference.attachment]?.file ?? null : null,
75 1 : kind,
76 1 : errors: reference.errors.map(String),
77 1 : };
78 1 : });
79 1 : reports.set(scripts[index].scriptKey, { textVersion: checked.textVersion, marks });
80 1 : });
81 1 : return reports;
82 1 : }
83 :
84 : /// A change of the text of a script, in UTF-16 code units of the text before the change
85 : export interface ScriptTextChange {
86 : from: number;
87 : to: number;
88 : insert: string;
89 : }
90 :
91 : /// What a table parameter reads, by file name: a script or an attachment
92 : export type TableParameterTarget = { script: string } | { attachment: string };
93 :
94 : /// The changes that rename the references to a script or an attachment: every table parameter that reads it reads
95 : /// `reference` instead, e.g. `:"foo-2"`
96 1 : export function renameReferenceChanges(
97 1 : reports: ReadonlyMap<number, ScriptReferenceReport>,
98 1 : target: TableParameterTarget,
99 1 : reference: string,
100 1 : ): Map<number, ScriptTextChange[]> {
101 1 : const reads = (mark: ScriptReferenceMark) => 'script' in target ? mark.target === target.script : mark.attachment === target.attachment;
102 1 : const out = new Map<number, ScriptTextChange[]>();
103 1 : for (const [scriptKey, report] of reports) {
104 1 : const changes = report.marks
105 1 : .filter(mark => reads(mark) && mark.parameter.length > 0)
106 1 : .map(mark => ({ from: mark.from, to: mark.to, insert: reference }));
107 1 : if (changes.length > 0) out.set(scriptKey, changes);
108 1 : }
109 1 : return out;
110 1 : }
111 :
112 : /// Apply changes to a text
113 1 : export function applyScriptTextChanges(text: string, changes: readonly ScriptTextChange[]): string {
114 1 : let out = text;
115 1 : for (const change of [...changes].sort((l, r) => r.from - l.from)) {
116 1 : out = out.slice(0, change.from) + change.insert + out.slice(change.to);
117 : }
118 1 : return out;
119 1 : }
|