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