Line data Source code
1 : //! The analyzer.
2 : //!
3 : //! The analysis runs three left-to-right passes over the post-order AST, interleaved per morsel of
4 : //! 1024 nodes: name resolution, function calls and constant propagation. The interleaving determines
5 : //! the expression ids. The visualizations of the `@vis` comment tags are built at the end.
6 : //!
7 : //! The scanned script is not modified. The analyzed script owns a copy of the names with the
8 : //! analyzer tags and the objects they resolve to.
9 :
10 : pub mod clfn_calls;
11 : mod constants;
12 : mod function_calls;
13 : pub mod manual_tag;
14 : pub mod param_tags;
15 : mod name_resolution;
16 : mod parameters;
17 : pub mod sql_types;
18 : mod state;
19 : pub mod types;
20 : mod vis_tags;
21 :
22 : pub use clfn_calls::ClientFunctionCall;
23 : pub use sql_types::{SqlType, SqlTypeKind};
24 : pub use types::*;
25 : pub use vis_tags::{has_vis_tags, statement_output_columns};
26 :
27 : use crate::catalog::*;
28 : use crate::error::Result;
29 : use crate::table_parameters::{TableParameters, TABLE_PARAMETERS_ENTRY_ID};
30 : use dashql_buffers::analyzer::{AnalyzerErrorSeverity, AnalyzerErrorType, NameTag, SemanticNodeMarkerType};
31 : use dashql_parser::names::{NameId, NameRegistry};
32 : use dashql_parser::parser::ParsedScript;
33 : use crate::hash::HashMap;
34 : use std::fmt;
35 : use std::sync::Arc;
36 :
37 : /// An analyzed script
38 : pub struct AnalyzedScript {
39 : /// The parsed script
40 : pub parsed: Arc<ParsedScript>,
41 : /// The catalog snapshot the script was analyzed against. Its locations into other entries refer
42 : /// to the declarations of this snapshot, however the catalog changed since. The snapshot holds
43 : /// declarations and names, never analyses, so it keeps no earlier analysis alive.
44 : pub snapshot: Arc<CatalogSnapshot>,
45 : /// The catalog declarations of the script, shared with the catalog when the script is published
46 : pub entry: Arc<CatalogEntryData>,
47 : /// The names of the scanned script, plus the empty name registered by the analyzer
48 : pub names: NameRegistry,
49 : /// The analyzer tags of the names
50 : pub name_tags: Vec<NameTag>,
51 : /// The objects of this script that names of this or other entries resolve to, in registration order
52 : pub name_objects: Vec<(CatalogEntryId, NameId, CatalogObjectRef)>,
53 : /// The empty name
54 : pub empty_name: NameId,
55 : /// The analyzer errors
56 : pub errors: Vec<AnalyzerError>,
57 : /// The semantic node markers
58 : pub node_markers: Vec<SemanticNodeMarkerType>,
59 : /// The table references
60 : pub table_references: Vec<TableReference>,
61 : /// The INSERT statements
62 : pub insert_statements: Vec<InsertStatement>,
63 : /// The expressions
64 : pub expressions: Vec<Expression>,
65 : /// The function arguments, referenced by function calls
66 : pub function_arguments: Vec<FunctionArgument>,
67 : /// The name scopes
68 : pub name_scopes: Vec<NameScope>,
69 : /// The name scopes by scope root
70 : pub name_scopes_by_root_node: HashMap<u32, ScopeId>,
71 : /// The constant expression roots
72 : pub constant_expressions: Vec<ConstantExpression>,
73 : /// The parameters, in the order of the script
74 : pub parameters: Vec<Parameter>,
75 : /// The calls in a namespace with client functions
76 : pub client_function_calls: Vec<ClientFunctionCall>,
77 : /// The visualization specs
78 : pub visualization_specs: Vec<VisualizationSpec>,
79 : /// The dashboard placement of the script, the `@board` tag of its last statement
80 : pub board: Option<BoardSpec>,
81 : /// The flow position of the script, the first `@flow` tag in statement order
82 : pub flow: Option<FlowSpec>,
83 : /// The values of the parameters of the script, the first `@param` tag of every name in statement order
84 : pub param_values: Vec<ParamTag>,
85 : /// Does the script run only on request? A `@manual` tag on any statement
86 : pub manual: bool,
87 : /// The schema-inference constraints
88 : pub inference_constraints: Vec<InferenceConstraint>,
89 : /// The inferred schemas of tables that are not in the catalog
90 : pub inferred_table_schemas: Vec<InferredTableSchema>,
91 : /// The inferred schemas by (database, schema, table)
92 : pub inferred_table_schemas_by_name: HashMap<QualifiedKey, usize>,
93 : /// The names of the script for completion, shared with the catalog when the script is published
94 : pub(crate) name_search_index: Arc<NameSearchIndex>,
95 : /// The results of the scripts of the session that table parameters read, the analysis is current with them only
96 : pub table_parameters: Option<Arc<TableParameters>>,
97 : }
98 :
99 : impl fmt::Debug for AnalyzedScript {
100 0 : fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 0 : f.debug_struct("AnalyzedScript")
102 0 : .field("catalog_entry_id", &self.entry.catalog_entry_id)
103 0 : .field("errors", &self.errors.len())
104 0 : .field("expressions", &self.expressions.len())
105 0 : .finish_non_exhaustive()
106 0 : }
107 : }
108 :
109 : impl AnalyzedScript {
110 909 : pub fn catalog_entry_id(&self) -> CatalogEntryId {
111 909 : self.entry.catalog_entry_id
112 909 : }
113 :
114 : /// The text of a name of this script
115 98 : pub fn name_text(&self, id: NameId) -> &str {
116 98 : &self.names.get(id).text
117 98 : }
118 :
119 : /// The name search index
120 249 : pub fn name_search_index(&self) -> &NameSearchIndex {
121 249 : &self.name_search_index
122 249 : }
123 :
124 : /// The part of the analysis that the catalog publishes
125 1037 : pub fn publication(&self) -> PublishedScript {
126 1037 : PublishedScript {
127 1037 : catalog_id: self.snapshot.id(),
128 1037 : entry: self.entry.clone(),
129 1037 : names: self.name_search_index.clone(),
130 1037 : }
131 1037 : }
132 :
133 : /// A table declared by this script or by an entry of its snapshot
134 62 : pub fn table(&self, loc: TableLoc) -> Option<&TableDeclaration> {
135 62 : if loc.entry == self.entry.catalog_entry_id {
136 16 : return self.entry.table_declarations.get(loc.index as usize);
137 46 : }
138 46 : if loc.entry == TABLE_PARAMETERS_ENTRY_ID {
139 18 : return self.table_parameters.as_ref()?.table(loc);
140 28 : }
141 28 : self.snapshot.table(loc)
142 62 : }
143 : }
144 :
145 : /// Analyze a parsed script against a catalog
146 739 : pub fn analyze(parsed: Arc<ParsedScript>, catalog: &Catalog) -> Result<AnalyzedScript> {
147 739 : analyze_with(parsed, catalog, None)
148 739 : }
149 :
150 : /// Analyze a parsed script against a catalog and the results of the scripts of its session, which its table
151 : /// parameters read
152 4003 : pub fn analyze_with(parsed: Arc<ParsedScript>, catalog: &Catalog, table_parameters: Option<&Arc<TableParameters>>) -> Result<AnalyzedScript> {
153 4003 : let mut state = state::AnalysisState::new(&parsed, catalog);
154 4003 : state.out.table_parameters = table_parameters.cloned();
155 4003 : let mut name_resolution = name_resolution::NameResolutionPass::new(&state);
156 4003 : let mut constants = constants::ConstantPropagationPass::default();
157 :
158 : // Scan all nodes in morsels, interleaving the passes
159 : const MORSEL_SIZE: usize = 1024;
160 4003 : let node_count = state.ast.len();
161 4003 : let mut begin = 0;
162 7858 : while begin != node_count {
163 3855 : let end = (begin + MORSEL_SIZE).min(node_count);
164 3855 : name_resolution.visit(&mut state, begin..end);
165 3855 : function_calls::visit(&mut state, begin..end)?;
166 3855 : constants.visit(&mut state, begin..end);
167 3855 : parameters::visit(&mut state, begin..end);
168 3855 : begin = end;
169 : }
170 4003 : name_resolution.finish(&mut state);
171 4003 : constants.finish(&mut state);
172 : // The AST is in post-order with sorted attributes, sort the parameters by their position in the text
173 4008 : state.out.parameters.sort_by_key(|p| p.location.offset());
174 4003 : vis_tags::analyze(&mut state);
175 4003 : param_tags::analyze(&mut state);
176 4003 : manual_tag::analyze(&mut state);
177 4003 : clfn_calls::analyze(&mut state);
178 :
179 4003 : let mut out = state.into_script();
180 : // Malformed comment tags fall back to ordinary comments, warn so that they don't go unnoticed
181 4003 : for (span, message) in &parsed.comment_blocks.warnings {
182 3 : out.errors.push(AnalyzerError {
183 3 : error_type: AnalyzerErrorType::MALFORMED_COMMENT_TAG,
184 3 : severity: AnalyzerErrorSeverity::WARNING,
185 3 : ast_node_id: None,
186 3 : symbol_span: None,
187 3 : text_span: Some(*span),
188 3 : message: message.clone(),
189 3 : });
190 3 : }
191 4003 : Ok(out)
192 4003 : }
193 :
194 : impl From<Arc<AnalyzedScript>> for PublishedScript {
195 1037 : fn from(script: Arc<AnalyzedScript>) -> Self {
196 1037 : script.publication()
197 1037 : }
198 : }
199 :
200 : impl From<&Arc<AnalyzedScript>> for PublishedScript {
201 0 : fn from(script: &Arc<AnalyzedScript>) -> Self {
202 0 : script.publication()
203 0 : }
204 : }
|