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

            Line data    Source code
       1              : //! The state shared between the analysis passes.
       2              : 
       3              : use super::types::*;
       4              : use super::AnalyzedScript;
       5              : use crate::catalog::*;
       6              : use dashql_buffers::analyzer::{AnalyzerErrorSeverity, AnalyzerErrorType, NameTag, SemanticNodeMarkerType};
       7              : use dashql_buffers::parser::{Node, NodeType, SymbolSpan};
       8              : use dashql_parser::names::NameId;
       9              : use dashql_parser::parser::context::resolve_text_span;
      10              : use dashql_parser::parser::ParsedScript;
      11              : use dashql_parser::scanner::ScannedScript;
      12              : use dashql_parser::span::TextSpan;
      13              : use crate::hash::HashMap;
      14              : use std::sync::Arc;
      15              : 
      16              : /// The state shared between the analysis passes of a script
      17              : pub(super) struct AnalysisState<'a> {
      18              :     /// The parsed script
      19              :     pub parsed: &'a ParsedScript,
      20              :     /// The AST
      21              :     pub ast: &'a [Node],
      22              :     /// The catalog
      23              :     pub catalog: &'a Catalog,
      24              :     /// The catalog entry id of the script
      25              :     pub entry_id: CatalogEntryId,
      26              :     /// The texts of the names
      27              :     pub name_texts: Vec<Arc<str>>,
      28              :     /// The output
      29              :     pub out: AnalyzedScript,
      30              :     /// The expression of every AST node, if it was translated
      31              :     pub expression_index: Vec<Option<ExprId>>,
      32              :     /// How often each qualified name was declared by a table so far
      33              :     table_name_occurrences: HashMap<QualifiedKey, u32>,
      34              :     /// How often each qualified name was declared by a function so far
      35              :     function_name_occurrences: HashMap<QualifiedKey, u32>,
      36              : }
      37              : 
      38              : impl<'a> AnalysisState<'a> {
      39         3734 :     pub fn new(parsed: &'a Arc<ParsedScript>, catalog: &'a Catalog) -> Self {
      40         3734 :         let scanned = parsed.scanned.clone();
      41         3734 :         let entry_id = scanned.external_id;
      42         3734 :         let mut names = scanned.names.clone();
      43         3734 :         let empty_name = names.register("", TextSpan::default());
      44         3734 :         let mut name_tags = vec![NameTag::NONE; names.len()];
      45         3734 :         name_tags[empty_name as usize] = NameTag(NameTag::DATABASE_NAME.0 | NameTag::SCHEMA_NAME.0);
      46        16296 :         let name_texts = names.iter().map(|n| Arc::<str>::from(n.text.as_str())).collect();
      47         3734 :         let node_count = parsed.nodes.len();
      48         3734 :         let out = AnalyzedScript {
      49         3734 :             parsed: parsed.clone(),
      50         3734 :             snapshot: catalog.snapshot().clone(),
      51         3734 :             entry: Arc::new(CatalogEntryData::new(entry_id, catalog.version())),
      52         3734 :             names,
      53         3734 :             name_tags,
      54         3734 :             name_objects: Vec::new(),
      55         3734 :             empty_name,
      56         3734 :             errors: Vec::new(),
      57         3734 :             node_markers: vec![SemanticNodeMarkerType::NONE; node_count],
      58         3734 :             table_references: Vec::new(),
      59         3734 :             insert_statements: Vec::new(),
      60         3734 :             expressions: Vec::new(),
      61         3734 :             function_arguments: Vec::new(),
      62         3734 :             name_scopes: Vec::new(),
      63         3734 :             name_scopes_by_root_node: Default::default(),
      64         3734 :             constant_expressions: Vec::new(),
      65         3734 :             parameters: Vec::new(),
      66         3734 :             visualization_specs: Vec::new(),
      67         3734 :             board: None,
      68         3734 :             flow: None,
      69         3734 :             param_values: Vec::new(),
      70         3734 :             manual: false,
      71         3734 :             inference_constraints: Vec::new(),
      72         3734 :             inferred_table_schemas: Vec::new(),
      73         3734 :             inferred_table_schemas_by_name: Default::default(),
      74         3734 :             name_search_index: Default::default(),
      75         3734 :             table_parameters: None,
      76         3734 :         };
      77         3734 :         Self {
      78         3734 :             parsed,
      79         3734 :             ast: &parsed.nodes,
      80         3734 :             catalog,
      81         3734 :             entry_id,
      82         3734 :             name_texts,
      83         3734 :             out,
      84         3734 :             expression_index: vec![None; node_count],
      85         3734 :             table_name_occurrences: HashMap::default(),
      86         3734 :             function_name_occurrences: HashMap::default(),
      87         3734 :         }
      88         3734 :     }
      89              : 
      90              :     /// Reserve the id of the next table that the script declares, keyed by its name and the
      91              :     /// occurrence of the name among the tables of the script
      92         1889 :     pub fn reserve_table_id(&mut self, name: &QualifiedTableName) -> CatalogTableId {
      93         1889 :         let key = name.key();
      94         1889 :         let occurrence = self.table_name_occurrences.entry(key.clone()).or_insert(0);
      95         1889 :         let id = self.catalog.reserve_table_id(self.entry_id, &key, *occurrence);
      96         1889 :         *occurrence += 1;
      97         1889 :         id
      98         1889 :     }
      99              : 
     100              :     /// Reserve the id of the next function that the script declares, like `reserve_table_id`
     101           30 :     pub fn reserve_function_id(&mut self, name: &QualifiedFunctionName) -> CatalogFunctionId {
     102           30 :         let key = name.key();
     103           30 :         let occurrence = self.function_name_occurrences.entry(key.clone()).or_insert(0);
     104           30 :         let id = self.catalog.reserve_function_id(self.entry_id, &key, *occurrence);
     105           30 :         *occurrence += 1;
     106           30 :         id
     107           30 :     }
     108              : 
     109              :     /// Reserve the ids of the columns of a table, keyed by their names and their occurrences
     110         1889 :     pub fn reserve_column_ids<'n>(&self, table: CatalogTableId, names: impl Iterator<Item = &'n Arc<str>>) -> Vec<u32> {
     111         1889 :         let mut occurrences: HashMap<&Arc<str>, u32> = HashMap::default();
     112         1889 :         names
     113         4172 :             .map(|name| {
     114         4172 :                 let occurrence = occurrences.entry(name).or_insert(0);
     115         4172 :                 let id = self.catalog.reserve_column_id(table, name, *occurrence);
     116         4172 :                 *occurrence += 1;
     117         4172 :                 id
     118         4172 :             })
     119         1889 :             .collect()
     120         1889 :     }
     121              : 
     122              :     /// Finish the analysis: collect the names of the script for completion
     123         3734 :     pub fn into_script(mut self) -> AnalyzedScript {
     124         3734 :         let out = &mut self.out;
     125         3734 :         let entry_id = out.entry.catalog_entry_id;
     126         3734 :         let mut names: Vec<IndexedName> = out
     127         3734 :             .names
     128         3734 :             .iter()
     129         3734 :             .map(|n| IndexedName {
     130        16296 :                 text: self.name_texts[n.name_id as usize].clone(),
     131        16296 :                 occurrences: n.occurrences,
     132        16296 :                 location: n.location,
     133        16296 :                 tags: out.name_tags[n.name_id as usize],
     134        16296 :                 objects: Vec::new(),
     135        16296 :             })
     136         3734 :             .collect();
     137         8833 :         for &(entry, name, object) in &out.name_objects {
     138         8606 :             if entry == entry_id {
     139         8528 :                 names[name as usize].objects.push(object);
     140         8528 :             }
     141              :         }
     142         3734 :         out.name_search_index = Arc::new(NameSearchIndex::new(names));
     143         3734 :         self.out
     144         3734 :     }
     145              : 
     146              :     /// The declarations of the script, unshared while the analysis runs
     147        14549 :     pub fn entry_mut(&mut self) -> &mut CatalogEntryData {
     148        14549 :         Arc::make_mut(&mut self.out.entry)
     149        14549 :     }
     150              : 
     151         8856 :     pub fn scanned(&self) -> &ScannedScript {
     152         8856 :         &self.parsed.scanned
     153         8856 :     }
     154              : 
     155              :     /// The children of a node
     156        44072 :     pub fn children(&self, node: &Node) -> &'a [Node] {
     157        44072 :         let count = node.children_count() as usize;
     158        44072 :         if count == 0 {
     159            0 :             return &[];
     160        44072 :         }
     161        44072 :         let begin = node.children_begin_or_value() as usize;
     162        44072 :         &self.ast[begin..begin + count]
     163        44072 :     }
     164              : 
     165              :     /// The id of a node of the AST
     166        15779 :     pub fn node_id(&self, node: &Node) -> u32 {
     167        15779 :         ((node as *const Node as usize - self.ast.as_ptr() as usize) / std::mem::size_of::<Node>()) as u32
     168        15779 :     }
     169              : 
     170              :     /// A name of the script
     171        24165 :     pub fn name(&self, id: NameId) -> Name {
     172        24165 :         Name {
     173        24165 :             entry: self.entry_id,
     174        24165 :             id,
     175        24165 :             text: self.name_texts[id as usize].clone(),
     176        24165 :         }
     177        24165 :     }
     178              : 
     179              :     /// The location of an object of this script
     180         2545 :     pub fn loc(&self, index: usize) -> ObjectLoc {
     181         2545 :         ObjectLoc {
     182         2545 :             entry: self.entry_id,
     183         2545 :             index: index as u32,
     184         2545 :         }
     185         2545 :     }
     186              : 
     187              :     /// The empty name
     188         9988 :     pub fn empty_name(&self) -> Name {
     189         9988 :         self.name(self.out.empty_name)
     190         9988 :     }
     191              : 
     192              :     /// The name of a NAME node
     193         4995 :     pub fn node_name(&self, node: &Node) -> Name {
     194         4995 :         self.name(node.children_begin_or_value())
     195         4995 :     }
     196              : 
     197              :     /// Add analyzer tags to a name of the script
     198        12711 :     pub fn tag(&mut self, name: &Name, tag: NameTag) {
     199        12711 :         if name.entry == self.entry_id {
     200        12711 :             let tags = &mut self.out.name_tags[name.id as usize];
     201        12711 :             *tags = NameTag(tags.0 | tag.0);
     202        12711 :         }
     203        12711 :     }
     204              : 
     205              :     /// Remember an object a name resolves to
     206         8606 :     pub fn resolve_name(&mut self, name: &Name, object: CatalogObjectRef) {
     207         8606 :         self.out.name_objects.push((name.entry, name.id, object));
     208         8606 :     }
     209              : 
     210              :     /// Resolve a symbol span to a text span
     211         8856 :     pub fn text_span(&self, span: SymbolSpan) -> TextSpan {
     212         8856 :         let scanned = self.scanned();
     213         8856 :         resolve_text_span(&scanned.text, &scanned.symbols, span)
     214         8856 :     }
     215              : 
     216              :     /// The text at a symbol span
     217           58 :     pub fn text_at(&self, span: SymbolSpan) -> &str {
     218           58 :         let text = &self.parsed.scanned.text;
     219           58 :         let ts = self.text_span(span);
     220           58 :         let begin = (ts.offset as usize).min(text.len());
     221           58 :         let end = (begin + ts.length as usize).min(text.len());
     222           58 :         &text[begin..end]
     223           58 :     }
     224              : 
     225              :     /// Mark a node
     226         8158 :     pub fn mark(&mut self, node_id: u32, marker: SemanticNodeMarkerType) {
     227         8158 :         self.out.node_markers[node_id as usize] = marker;
     228         8158 :     }
     229              : 
     230              :     /// Add an expression
     231         5894 :     pub fn add_expression(&mut self, node_id: u32, location: SymbolSpan, inner: ExpressionInner) -> ExprId {
     232         5894 :         let id = self.out.expressions.len() as ExprId;
     233         5894 :         self.out.expressions.push(Expression {
     234         5894 :             expression_id: id,
     235         5894 :             ast_node_id: node_id,
     236         5894 :             location: Some(location),
     237         5894 :             ast_statement_id: None,
     238         5894 :             inner,
     239         5894 :             is_constant_expression: false,
     240         5894 :         });
     241         5894 :         id
     242         5894 :     }
     243              : 
     244              :     /// The expression of a node
     245         8262 :     pub fn expression_of(&self, node_id: u32) -> Option<ExprId> {
     246         8262 :         self.expression_index.get(node_id as usize).copied().flatten()
     247         8262 :     }
     248              : 
     249              :     /// Add an error at an AST node
     250           38 :     pub fn add_error(&mut self, error_type: AnalyzerErrorType, severity: AnalyzerErrorSeverity, node_id: u32, message: String) {
     251           38 :         let span = *self.ast[node_id as usize].symbol_span();
     252           38 :         let text_span = self.text_span(span);
     253           38 :         self.out.errors.push(AnalyzerError {
     254           38 :             error_type,
     255           38 :             severity,
     256           38 :             ast_node_id: Some(node_id),
     257           38 :             symbol_span: Some(span),
     258           38 :             text_span: Some(text_span),
     259           38 :             message,
     260           38 :         });
     261           38 :     }
     262              : 
     263              :     /// Read a name path
     264         8340 :     pub fn read_name_path(&self, node: &Node) -> Vec<NameId> {
     265         8340 :         let mut path = Vec::new();
     266         8340 :         if node.node_type() != NodeType::ARRAY {
     267            0 :             return path;
     268         8340 :         }
     269         9222 :         for child in self.children(node) {
     270         9222 :             match child.node_type() {
     271              :                 // Skip over trailing dots
     272           38 :                 NodeType::OBJECT_EXT_TRAILING_DOT => continue,
     273         9184 :                 NodeType::NAME => path.push(child.children_begin_or_value()),
     274              :                 // Not a plain name path
     275              :                 _ => {
     276            0 :                     path.clear();
     277            0 :                     break;
     278              :                 }
     279              :             }
     280              :         }
     281         8340 :         path
     282         8340 :     }
     283              : 
     284              :     /// Read a qualified table name
     285         5259 :     pub fn read_qualified_table_name(&mut self, node: Option<&Node>) -> Option<QualifiedTableName> {
     286         5259 :         let node = node?;
     287         4414 :         let path = self.read_name_path(node);
     288         4414 :         let ast_node_id = Some(self.node_id(node));
     289         4414 :         let (db, schema, table) = match path.len() {
     290          198 :             3 => (self.name(path[0]), self.name(path[1]), self.name(path[2])),
     291           98 :             2 => (self.empty_name(), self.name(path[0]), self.name(path[1])),
     292         4118 :             1 => (self.empty_name(), self.empty_name(), self.name(path[0])),
     293            0 :             _ => return None,
     294              :         };
     295         4414 :         if path.len() == 3 {
     296          198 :             self.tag(&db, NameTag::DATABASE_NAME);
     297         4217 :         }
     298         4414 :         if path.len() >= 2 {
     299          296 :             self.tag(&schema, NameTag::SCHEMA_NAME);
     300         4123 :         }
     301         4414 :         self.tag(&table, NameTag::TABLE_NAME);
     302         4414 :         Some(QualifiedTableName {
     303         4414 :             ast_node_id,
     304         4414 :             database_name: db,
     305         4414 :             schema_name: schema,
     306         4414 :             table_name: table,
     307         4414 :         })
     308         5259 :     }
     309              : 
     310              :     /// Read a qualified column name.
     311              :     /// Paths with more than two names access fields of a row-typed column.
     312         2665 :     pub fn read_qualified_column_name(&mut self, node: Option<&Node>) -> Option<QualifiedColumnName> {
     313         2665 :         let node = node?;
     314         2665 :         let path = self.read_name_path(node);
     315         2665 :         let ast_node_id = Some(self.node_id(node));
     316         2665 :         match path.len() {
     317            0 :             0 => None,
     318              :             1 => {
     319         2332 :                 let column = self.name(path[0]);
     320         2332 :                 self.tag(&column, NameTag::COLUMN_NAME);
     321         2332 :                 Some(QualifiedColumnName {
     322         2332 :                     ast_node_id,
     323         2332 :                     table_alias: None,
     324         2332 :                     column_name: column,
     325         2332 :                 })
     326              :             }
     327              :             _ => {
     328          333 :                 let alias = self.name(path[0]);
     329          333 :                 let column = self.name(path[1]);
     330          333 :                 self.tag(&alias, NameTag::TABLE_ALIAS);
     331          333 :                 self.tag(&column, NameTag::COLUMN_NAME);
     332          333 :                 Some(QualifiedColumnName {
     333          333 :                     ast_node_id,
     334          333 :                     table_alias: Some(alias),
     335          333 :                     column_name: column,
     336          333 :                 })
     337              :             }
     338              :         }
     339         2665 :     }
     340              : 
     341              :     /// Read a qualified function name
     342          365 :     pub fn read_qualified_function_name(&mut self, node: Option<&Node>) -> Option<QualifiedFunctionName> {
     343          365 :         let node = node?;
     344          365 :         let path = self.read_name_path(node);
     345          365 :         let ast_node_id = Some(self.node_id(node));
     346          365 :         let (db, schema, function) = match path.len() {
     347            5 :             3 => (self.name(path[0]), self.name(path[1]), self.name(path[2])),
     348            5 :             2 => (self.empty_name(), self.name(path[0]), self.name(path[1])),
     349          355 :             1 => (self.empty_name(), self.empty_name(), self.name(path[0])),
     350            0 :             _ => return None,
     351              :         };
     352          365 :         if path.len() == 3 {
     353            5 :             self.tag(&db, NameTag::DATABASE_NAME);
     354          360 :         }
     355          365 :         if path.len() >= 2 {
     356           10 :             self.tag(&schema, NameTag::SCHEMA_NAME);
     357          357 :         }
     358          365 :         self.tag(&function, NameTag::FUNCTION_NAME);
     359          365 :         Some(QualifiedFunctionName {
     360          365 :             ast_node_id,
     361          365 :             database_name: db,
     362          365 :             schema_name: schema,
     363          365 :             function_name: function,
     364          365 :         })
     365          365 :     }
     366              : 
     367              :     /// A table of this script or of the catalog
     368         7927 :     pub fn table(&self, loc: TableLoc) -> Option<&TableDeclaration> {
     369         7927 :         if loc.entry == self.entry_id {
     370          450 :             self.out.entry.table_declarations.get(loc.index as usize)
     371         7477 :         } else if loc.entry == crate::table_parameters::TABLE_PARAMETERS_ENTRY_ID {
     372           30 :             self.out.table_parameters.as_ref()?.table(loc)
     373              :         } else {
     374         7447 :             self.catalog.table(loc)
     375              :         }
     376         7927 :     }
     377              : 
     378              :     /// The statement that contains an AST node, None outside of statements
     379        10571 :     pub fn find_statement_id(&self, ast_node_id: u32) -> Option<u32> {
     380        10571 :         let mut root = ast_node_id;
     381              :         // Statement roots are their own parents
     382        10571 :         for _ in 0..self.ast.len() {
     383        15776 :             let parent = self.ast[root as usize].parent();
     384        15776 :             if parent as usize >= self.ast.len() || parent == root {
     385        10571 :                 break;
     386         5205 :             }
     387         5205 :             root = parent;
     388              :         }
     389        10571 :         let statements = &self.parsed.statements;
     390        17528 :         if let Some(i) = statements.iter().position(|s| s.root == root) {
     391        10563 :             return Some(i as u32);
     392            8 :         }
     393            8 :         statements
     394            8 :             .iter()
     395            8 :             .position(|s| ast_node_id >= s.nodes_begin && ast_node_id < s.nodes_begin + s.node_count)
     396            8 :             .map(|i| i as u32)
     397        10571 :     }
     398              : }
     399              : 
     400              : /// The literal type of a literal node type
     401         2801 : pub(super) fn literal_type(node_type: NodeType) -> dashql_buffers::algebra::LiteralType {
     402         2801 :     dashql_buffers::algebra::LiteralType((node_type.0 - 5) as u8)
     403         2801 : }
     404              : 
     405              : pub(super) use crate::utils::quote_identifier;
        

Generated by: LCOV version 2.0-1