LCOV - code coverage report
Current view: top level - dashql-core/src/analyzer - mod.rs Coverage Total Hit
Test: DashQL 915828b Lines: 86.5 % 74 64
Test Date: 2026-11-06 19:30:32

            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              : }
        

Generated by: LCOV version 2.0-1