LCOV - code coverage report
Current view: top level - dashql-core/src/analyzer - mod.rs Coverage Total Hit
Test: DashQL d805a4b Lines: 86.7 % 75 65
Test Date: 2026-11-09 20:22:13

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

Generated by: LCOV version 2.0-1