LCOV - code coverage report
Current view: top level - dashql-core/src - pack.rs Coverage Total Hit
Test: DashQL d805a4b Lines: 95.9 % 852 817
Test Date: 2026-11-09 20:22:13

            Line data    Source code
       1              : //! FlatBuffer packing of scanned, parsed and analyzed scripts, tokens, cursors and completions.
       2              : //!
       3              : //! The parsed script leaves out empty vectors and strings, the analyzed script always writes its
       4              : //! vectors.
       5              : 
       6              : // Bison names some symbol kinds in mixed case (S_Op)
       7              : #![allow(non_upper_case_globals)]
       8              : 
       9              : use crate::analyzer::*;
      10              : use crate::catalog::*;
      11              : use dashql_buffers::algebra::{
      12              :     BinaryExpressionT, ColumnRefExpressionT, ComparisonT, ConstIntervalCastT, ExpressionSubTypeT, ExpressionT,
      13              :     FunctionCallExpressionT, LiteralT, ResolvedColumnT,
      14              : };
      15              : use dashql_buffers::analyzer::{
      16              :     AnalyzedScriptT, AnalyzerErrorT, ConstantExpressionT, FunctionDeclarationT, FunctionParamT, IndexedColumnReferenceT,
      17              :     ClientFunctionCallT, ClientFunctionEffect as PackedClientFunctionEffect,
      18              :     IndexedTableReferenceT, InferredColumnT, InferredTableSchemaT, InsertStatementT, InsertTargetColumnT, NameScopeT,
      19              :     ParameterKind as PackedParameterKind, ParameterT, SqlTypeKind as PackedSqlTypeKind, SqlTypeT,
      20              :     QualifiedColumnNameT, QualifiedFunctionNameT, QualifiedTableNameT, ResolvedTableT, TableColumnT,
      21              :     TableReferenceRole, TableReferenceT, TableT, VisualizationSpecT,
      22              : };
      23              : use dashql_buffers::parser::{
      24              :     AttachDatabaseStatementT, AttributeKey, CommentBlockT, ErrorT, Node, NodeT, NodeType,
      25              :     ParsedScriptT, ScannedScriptT, ScannerTokenType, ScannerTokensT, StatementT, StatementTargetT, StatementTargetType,
      26              :     StatementType, SymbolSpan, SymbolSpanT, TextSpanT,
      27              : };
      28              : use dashql_buffers::visualization::{DashboardSpecT, TableSpecT};
      29              : use dashql_parser::keywords::Keyword;
      30              : use dashql_parser::parser::context::resolve_text_span;
      31              : use dashql_parser::parser::ParsedScript;
      32              : use dashql_parser::scanner::{ScannedScript, Symbol};
      33              : use dashql_parser::span::TextSpan;
      34              : use dashql_parser::symbol_kind::*;
      35              : 
      36              : /// Build a FlatBuffers object API value, which are non-exhaustive: start from the default and
      37              : /// assign the listed fields
      38              : macro_rules! build {
      39              :     ($ty:ident { $($field:ident $(: $value:expr)?),* $(,)? }) => {{
      40              :         let mut v = $ty::default();
      41              :         $( build!(@set v, $field $(, $value)?); )*
      42              :         v
      43              :     }};
      44              :     (@set $v:ident, $field:ident, $value:expr) => { $v.$field = $value; };
      45              :     (@set $v:ident, $field:ident) => { $v.$field = $field; };
      46              : }
      47              : 
      48              : /// A vector, left out when empty
      49         2029 : fn non_empty<T>(v: Vec<T>) -> Option<Vec<T>> {
      50         2029 :     (!v.is_empty()).then_some(v)
      51         2029 : }
      52              : 
      53              : /// A string, left out when empty
      54         3072 : fn non_empty_str(s: &str) -> Option<String> {
      55         3072 :     (!s.is_empty()).then(|| s.to_string())
      56         3072 : }
      57              : 
      58           98 : fn text_span(span: TextSpan) -> TextSpanT {
      59           98 :     build!(TextSpanT { offset: span.offset, length: span.length })
      60           98 : }
      61              : 
      62         9844 : fn symbol_span(span: SymbolSpan) -> SymbolSpanT {
      63         9844 :     build!(SymbolSpanT { offset: span.offset(), length: span.length() })
      64         9844 : }
      65              : 
      66              : /// The index of a node in its AST
      67          962 : fn n_index(nodes: &[Node], n: &Node) -> usize {
      68          962 :     (n as *const Node as usize - nodes.as_ptr() as usize) / std::mem::size_of::<Node>()
      69          962 : }
      70              : 
      71            6 : fn comment_block(b: &dashql_parser::comment_blocks::CommentBlock) -> CommentBlockT {
      72            6 :     build!(CommentBlockT {
      73            6 :         block_type: b.block_type,
      74            6 :         text_span: text_span(b.text_span),
      75            6 :         tag_span: text_span(b.tag_span),
      76            6 :         comments_begin: b.comments_begin,
      77            6 :         comments_count: b.comments_count,
      78            6 :         lines_begin: b.lines_begin,
      79            6 :         lines_count: b.lines_count,
      80              :     })
      81            6 : }
      82              : 
      83          178 : fn node(n: &Node) -> NodeT {
      84          178 :     n.unpack()
      85          178 : }
      86              : 
      87              : /// Pack a scanned script
      88            6 : pub fn pack_scanned_script(scanned: &ScannedScript) -> ScannedScriptT {
      89            6 :     build!(ScannedScriptT {
      90            6 :         external_id: scanned.external_id,
      91            6 :         line_breaks: non_empty(scanned.line_breaks.iter().map(|&s| text_span(s)).collect()),
      92            6 :         errors: non_empty(
      93            6 :             scanned
      94            6 :                 .errors
      95            6 :                 .iter()
      96            6 :                 .map(|e| build!(ErrorT {
      97            2 :                     text_span: Some(text_span(e.location)),
      98            2 :                     symbol_span: None,
      99            2 :                     message: non_empty_str(&e.message),
     100            2 :                     hint: None,
     101              :                 }))
     102            6 :                 .collect(),
     103              :         ),
     104            6 :         comments: non_empty(scanned.comments.iter().map(|&s| text_span(s)).collect()),
     105              :     })
     106            6 : }
     107              : 
     108              : /// The token type of a scanner symbol
     109         2283 : fn token_type(symbol: &Symbol, text: &str) -> ScannerTokenType {
     110         2283 :     match symbol.kind {
     111            0 :         S_NULLS_LA | S_NOT_LA | S_WITH_LA => ScannerTokenType::KEYWORD,
     112         2283 :         kind if Keyword::name_of(kind).is_some() => ScannerTokenType::KEYWORD,
     113           18 :         S_SCONST | S_DCONST => ScannerTokenType::LITERAL_STRING,
     114           77 :         S_ICONST => ScannerTokenType::LITERAL_INTEGER,
     115            3 :         S_FCONST => ScannerTokenType::LITERAL_FLOAT,
     116            3 :         S_BCONST => ScannerTokenType::LITERAL_BINARY,
     117            3 :         S_XCONST => ScannerTokenType::LITERAL_HEX,
     118          786 :         S_IDENT => ScannerTokenType::IDENTIFIER,
     119            2 :         S_Op | S_EQUALS_GREATER | S_GREATER_EQUALS | S_LESS_EQUALS | S_NOT_EQUALS => ScannerTokenType::OPERATOR,
     120          154 :         S_DOT | S_DOT_STAR_LA => ScannerTokenType::DOT,
     121            2 :         S_DOT_TRAILING => ScannerTokenType::DOT_TRAILING,
     122              :         _ => {
     123          427 :             if symbol.location.length == 1 && text.as_bytes().get(symbol.location.offset as usize) == Some(&b'=') {
     124           23 :                 ScannerTokenType::OPERATOR
     125              :             } else {
     126          404 :                 ScannerTokenType::NONE
     127              :             }
     128              :         }
     129              :     }
     130         2283 : }
     131              : 
     132              : /// Pack the tokens for highlighting
     133          460 : pub fn pack_tokens(parsed: &ParsedScript) -> ScannerTokensT {
     134          460 :     let scanned = &parsed.scanned;
     135          460 :     let symbol_count = scanned.symbols.len();
     136              :     // Keywords that the parser used as names are identifiers
     137          460 :     let mut name_overrides = vec![false; symbol_count];
     138         3121 :     for n in &parsed.nodes {
     139         3121 :         if n.node_type() == NodeType::NAME && n.symbol_span().length() == 1 {
     140          676 :             if let Some(slot) = name_overrides.get_mut(n.symbol_span().offset() as usize) {
     141          676 :                 *slot = true;
     142          676 :             }
     143         2445 :         }
     144              :     }
     145          460 :     let count = symbol_count.saturating_sub(1);
     146          460 :     let mut offsets = Vec::with_capacity(count);
     147          460 :     let mut lengths = Vec::with_capacity(count);
     148          460 :     let mut types = Vec::with_capacity(count);
     149         2283 :     for (id, symbol) in scanned.symbols[..count].iter().enumerate() {
     150         2283 :         offsets.push(symbol.location.offset);
     151         2283 :         lengths.push(symbol.location.length);
     152         2283 :         let mut t = token_type(symbol, &scanned.text);
     153         2283 :         if t == ScannerTokenType::KEYWORD && name_overrides[id] {
     154            9 :             t = ScannerTokenType::IDENTIFIER;
     155         2274 :         }
     156         2283 :         types.push(t);
     157              :     }
     158              :     // The token index of every line break
     159          460 :     let mut breaks = Vec::with_capacity(scanned.line_breaks.len());
     160          460 :     let mut oi = 0;
     161          460 :     for lb in &scanned.line_breaks {
     162          176 :         while oi < offsets.len() && offsets[oi] < lb.offset {
     163          109 :             oi += 1;
     164          109 :         }
     165           67 :         breaks.push(oi as u32);
     166              :     }
     167          460 :     build!(ScannerTokensT {
     168          460 :         token_offsets: non_empty(offsets),
     169          460 :         token_lengths: non_empty(lengths),
     170          460 :         token_types: non_empty(types),
     171          460 :         token_breaks: non_empty(breaks),
     172              :     })
     173          460 : }
     174              : 
     175              : /// Pack a statement with its target
     176          974 : fn pack_statement(parsed: &ParsedScript, statement_id: usize) -> StatementT {
     177          974 :     let statement = &parsed.statements[statement_id];
     178          974 :     let nodes = &parsed.nodes;
     179          974 :     let mut out = build!(StatementT {
     180          974 :         statement_type: statement.statement_type,
     181          974 :         root_node: statement.root,
     182          974 :         nodes_begin: statement.nodes_begin,
     183          974 :         node_count: statement.node_count,
     184              :     });
     185          974 :     let root = &nodes[statement.root as usize];
     186         1946 :     fn children<'a>(nodes: &'a [Node], n: &Node) -> &'a [Node] {
     187         1946 :         let begin = n.children_begin_or_value() as usize;
     188         1946 :         &nodes[begin..begin + n.children_count() as usize]
     189         1946 :     }
     190          995 :     let find_attribute = |key: AttributeKey| children(nodes, root).iter().find(|c| c.attribute_key() == key);
     191              :     // Follow the left inputs of set operations
     192          974 :     let find_select_attribute = |key: AttributeKey| -> Option<&Node> {
     193           10 :         let mut current = root;
     194           11 :         while current.node_type() == NodeType::OBJECT_SQL_SELECT {
     195           11 :             let mut next = None;
     196           22 :             for child in children(nodes, current) {
     197           22 :                 if child.attribute_key() == key {
     198           10 :                     return Some(child);
     199           12 :                 }
     200           12 :                 if child.attribute_key() == AttributeKey::SQL_COMBINE_INPUT
     201            1 :                     && child.node_type() == NodeType::ARRAY
     202            1 :                     && child.children_count() > 0
     203            1 :                 {
     204            1 :                     next = Some(&nodes[child.children_begin_or_value() as usize]);
     205           11 :                 }
     206              :             }
     207            1 :             current = next?;
     208              :         }
     209            0 :         None
     210           10 :     };
     211         1106 :     let read_name = |source: Option<&Node>| -> String {
     212         1104 :         match source {
     213         1104 :             Some(n) if n.node_type() == NodeType::NAME => {
     214         1104 :                 parsed.scanned.names.get(n.children_begin_or_value()).text.clone()
     215              :             }
     216            0 :             _ => String::new(),
     217              :         }
     218         1104 :     };
     219          974 :     let read_target = |out: &mut StatementT, source: Option<&Node>| {
     220          962 :         let mut source: Option<&Node> = source.map(|n| &nodes[n_index(nodes, n)]);
     221          962 :         if let Some(s) = source.filter(|s| s.node_type() == NodeType::OBJECT_SQL_TABLEREF) {
     222            2 :             if let Some(name) = children(nodes, s).iter().find(|c| c.attribute_key() == AttributeKey::SQL_TABLEREF_NAME)
     223            2 :             {
     224            2 :                 source = Some(name);
     225            2 :             }
     226          960 :         }
     227          962 :         let Some(source) = source.filter(|s| s.node_type() == NodeType::ARRAY) else { return };
     228          962 :         let names = children(nodes, source);
     229          962 :         if names.is_empty() || names.len() > 3 {
     230            0 :             return;
     231          962 :         }
     232          962 :         let relation_name = read_name(names.last());
     233          962 :         let schema_name = if names.len() >= 2 { read_name(Some(&names[names.len() - 2])) } else { String::new() };
     234          962 :         let database_name = if names.len() == 3 { read_name(Some(&names[0])) } else { String::new() };
     235          962 :         if !relation_name.is_empty() {
     236          962 :             out.target = Some(Box::new(build!(StatementTargetT {
     237          962 :                 database_name: non_empty_str(&database_name),
     238          962 :                 schema_name: non_empty_str(&schema_name),
     239          962 :                 relation_name: non_empty_str(&relation_name),
     240          962 :             })));
     241          962 :         }
     242          962 :     };
     243          974 :     let set_target = |out: &mut StatementT, target_type: StatementTargetType, key: AttributeKey| {
     244          952 :         out.target_type = target_type;
     245          952 :         read_target(out, find_attribute(key));
     246          952 :     };
     247          974 :     match statement.statement_type {
     248              :         StatementType::CREATE_TABLE => {
     249          801 :             set_target(&mut out, StatementTargetType::TABLE, AttributeKey::SQL_CREATE_TABLE_NAME)
     250              :         }
     251              :         StatementType::CREATE_TABLE_AS => {
     252           35 :             set_target(&mut out, StatementTargetType::TABLE, AttributeKey::SQL_CREATE_AS_NAME)
     253              :         }
     254           24 :         StatementType::CREATE_VIEW => set_target(&mut out, StatementTargetType::VIEW, AttributeKey::SQL_VIEW_NAME),
     255           45 :         StatementType::DROP_TABLE => set_target(&mut out, StatementTargetType::TABLE, AttributeKey::SQL_DROP_NAME),
     256            9 :         StatementType::DROP_VIEW => set_target(&mut out, StatementTargetType::VIEW, AttributeKey::SQL_DROP_NAME),
     257           34 :         StatementType::ALTER_TABLE => set_target(&mut out, StatementTargetType::TABLE, AttributeKey::SQL_ALTER_TABLE_NAME),
     258            2 :         StatementType::ALTER_VIEW => set_target(&mut out, StatementTargetType::VIEW, AttributeKey::SQL_ALTER_TABLE_NAME),
     259              :         StatementType::SELECT_INTO => {
     260           10 :             if let Some(into) =
     261           10 :                 find_select_attribute(AttributeKey::SQL_SELECT_INTO).filter(|n| n.node_type() != NodeType::NONE)
     262              :             {
     263           10 :                 out.target_type = StatementTargetType::TABLE;
     264           10 :                 if let Some(name) =
     265           20 :                     children(nodes, into).iter().find(|c| c.attribute_key() == AttributeKey::SQL_TEMP_NAME)
     266           10 :                 {
     267           10 :                     read_target(&mut out, Some(name));
     268           10 :                 }
     269            0 :             }
     270              :         }
     271            3 :         StatementType::ATTACH_DATABASE => {
     272            3 :             out.attach_database = Some(Box::new(build!(AttachDatabaseStatementT {
     273            3 :                 path: non_empty_str(&read_name(find_attribute(AttributeKey::SQL_ATTACH_DATABASE_PATH))),
     274            3 :                 alias: non_empty_str(&read_name(find_attribute(AttributeKey::SQL_ATTACH_DATABASE_ALIAS))),
     275            3 :                 local: find_attribute(AttributeKey::SQL_ATTACH_DATABASE_LOCAL).is_some(),
     276            3 :             })));
     277            3 :         }
     278            1 :         StatementType::INSERT => set_target(&mut out, StatementTargetType::TABLE, AttributeKey::SQL_INSERT_TARGET),
     279            1 :         StatementType::DELETE => set_target(&mut out, StatementTargetType::TABLE, AttributeKey::SQL_DELETE_TARGET),
     280            9 :         _ => {}
     281              :     }
     282          974 :     out
     283          974 : }
     284              : 
     285              : /// The statement of a parsed script with its target
     286              : /// The parts of a name: a name node, or the array of names of a qualified name
     287           20 : pub fn read_name_path(parsed: &ParsedScript, node: &Node) -> Vec<String> {
     288           23 :     let name = |n: &Node| parsed.scanned.names.get(n.children_begin_or_value()).text.clone();
     289           20 :     match node.node_type() {
     290            6 :         NodeType::NAME => vec![name(node)],
     291           17 :         NodeType::ARRAY => parsed.children(node).iter().filter(|n| n.node_type() == NodeType::NAME).map(name).collect(),
     292            0 :         _ => Vec::new(),
     293              :     }
     294           20 : }
     295              : 
     296          954 : pub fn pack_parsed_statement(parsed: &ParsedScript, statement_id: usize) -> StatementT {
     297          954 :     pack_statement(parsed, statement_id)
     298          954 : }
     299              : 
     300              : /// Pack a parsed script
     301           12 : pub fn pack_parsed_script(parsed: &ParsedScript) -> ParsedScriptT {
     302           12 :     let scanned = &parsed.scanned;
     303           12 :     let descriptions = parsed.associate_descriptions();
     304           12 :     let statements = (0..parsed.statements.len())
     305           21 :         .map(|i| {
     306           20 :             let mut statement = pack_statement(parsed, i);
     307           20 :             let metadata = &descriptions[i];
     308           20 :             statement.statement_span = Some(text_span(metadata.statement_span));
     309           20 :             statement.description_begin = metadata.description_begin;
     310           20 :             statement.description_count = metadata.description_count;
     311           20 :             statement.description_blocks_begin = metadata.description_blocks_begin;
     312           20 :             statement.description_blocks_count = metadata.description_blocks_count;
     313           20 :             statement
     314           20 :         })
     315           12 :         .collect();
     316           12 :     let scanner_errors = scanned
     317           12 :         .errors
     318           12 :         .iter()
     319           12 :         .map(|e| {
     320            1 :             build!(ErrorT {
     321            1 :                 text_span: Some(text_span(e.location)),
     322            1 :                 symbol_span: None,
     323            1 :                 message: non_empty_str(&e.message),
     324            1 :                 hint: None,
     325              :             })
     326            1 :         })
     327           12 :         .collect();
     328           12 :     let parser_errors = parsed
     329           12 :         .errors
     330           12 :         .iter()
     331           12 :         .map(|e| {
     332            3 :             build!(ErrorT {
     333            3 :                 symbol_span: Some(symbol_span(e.location)),
     334            3 :                 text_span: Some(text_span(resolve_text_span(&scanned.text, &scanned.symbols, e.location))),
     335            3 :                 message: non_empty_str(&e.message),
     336            3 :                 hint: non_empty_str(&e.hint),
     337              :             })
     338            3 :         })
     339           12 :         .collect();
     340           12 :     let blocks = &parsed.comment_blocks;
     341           12 :     build!(ParsedScriptT {
     342           12 :         external_id: scanned.external_id,
     343           12 :         nodes: non_empty(parsed.nodes.iter().map(node).collect()),
     344           12 :         statements: non_empty(statements),
     345           12 :         scanner_errors: non_empty(scanner_errors),
     346           12 :         parser_errors: non_empty(parser_errors),
     347           12 :         tokens: Some(Box::new(pack_tokens(parsed))),
     348           12 :         line_breaks: non_empty(scanned.line_breaks.iter().map(|&s| text_span(s)).collect()),
     349           12 :         comments: non_empty(scanned.comments.iter().map(|&s| text_span(s)).collect()),
     350           12 :         comment_blocks: non_empty(blocks.blocks.iter().map(comment_block).collect()),
     351           12 :         comment_lines: non_empty(blocks.lines.iter().map(|&s| text_span(s)).collect()),
     352              :     })
     353           12 : }
     354              : 
     355              : /// A packed name, absent if empty
     356          169 : fn name(n: &Name) -> Option<String> {
     357          169 :     non_empty_str(&n.text)
     358          169 : }
     359              : 
     360           40 : fn qualified_table_name(n: &QualifiedTableName) -> QualifiedTableNameT {
     361           40 :     build!(QualifiedTableNameT {
     362           40 :         ast_node_id: n.ast_node_id.unwrap_or(PROTO_NULL_U32),
     363           40 :         database_name: name(&n.database_name),
     364           40 :         schema_name: name(&n.schema_name),
     365           40 :         table_name: name(&n.table_name),
     366              :     })
     367           40 : }
     368              : 
     369           27 : fn qualified_column_name(n: &QualifiedColumnName) -> QualifiedColumnNameT {
     370           27 :     build!(QualifiedColumnNameT {
     371           27 :         ast_node_id: n.ast_node_id.unwrap_or(PROTO_NULL_U32),
     372           27 :         table_alias: n.table_alias.as_ref().and_then(name),
     373           27 :         column_name: name(&n.column_name),
     374              :     })
     375           27 : }
     376              : 
     377            6 : fn qualified_function_name(n: &QualifiedFunctionName) -> QualifiedFunctionNameT {
     378            6 :     build!(QualifiedFunctionNameT {
     379            6 :         ast_node_id: n.ast_node_id.unwrap_or(PROTO_NULL_U32),
     380            6 :         database_name: name(&n.database_name),
     381            6 :         schema_name: name(&n.schema_name),
     382            6 :         function_name: name(&n.function_name),
     383              :     })
     384            6 : }
     385              : 
     386              : /// Pack a function declaration
     387            2 : pub fn pack_function_declaration(f: &FunctionDeclaration) -> FunctionDeclarationT {
     388            2 :     build!(FunctionDeclarationT {
     389            2 :         ast_node_id: f.ast_node_id.unwrap_or(PROTO_NULL_U32),
     390            2 :         ast_statement_id: f.ast_statement_id.unwrap_or(PROTO_NULL_U32),
     391            2 :         is_aggregate: f.is_aggregate,
     392            2 :         function_name: Some(Box::new(qualified_function_name(&f.function_name))),
     393              :         params: Some(
     394            2 :             f.params
     395            2 :                 .iter()
     396            2 :                 .map(|p| build!(FunctionParamT {
     397            2 :                     ast_node_id: p.ast_node_id.unwrap_or(PROTO_NULL_U32),
     398            2 :                     param_name: Some(p.param_name.text.to_string()),
     399            2 :                     param_type: Some(p.param_type.to_string()),
     400              :                 }))
     401            2 :                 .collect(),
     402              :         ),
     403            2 :         return_type: non_empty_str(&f.return_type),
     404            2 :         location: f.location.map(text_span),
     405              :     })
     406            2 : }
     407              : 
     408              : /// Pack a table declaration
     409           14 : pub fn pack_table(table: &TableDeclaration) -> TableT {
     410           14 :     let (db, schema) = table.catalog_schema_id.unpack_schema_id();
     411           14 :     build!(TableT {
     412           14 :         catalog_table_id: table.table_id().pack(),
     413           14 :         catalog_database_id: db,
     414           14 :         catalog_schema_id: schema,
     415           14 :         ast_node_id: table.ast_node_id.unwrap_or(PROTO_NULL_U32),
     416           14 :         ast_statement_id: table.ast_statement_id.unwrap_or(PROTO_NULL_U32),
     417           14 :         ast_scope_root: table.ast_scope_root.unwrap_or(PROTO_NULL_U32),
     418           14 :         table_name: Some(Box::new(qualified_table_name(&table.table_name))),
     419              :         // The column names are always written, the column AST nodes are not
     420              :         table_columns: Some(
     421           14 :             table
     422           14 :                 .table_columns
     423           14 :                 .iter()
     424           20 :                 .map(|c| build!(TableColumnT {
     425           20 :                     column_name: Some(c.column_name.text.to_string()),
     426           20 :                     location: c.location.map(text_span),
     427           20 :                     column_type: c.column_type.as_deref().map(str::to_string),
     428              :                 }))
     429           14 :                 .collect(),
     430              :         ),
     431           14 :         location: table.location.map(text_span),
     432              :     })
     433           14 : }
     434              : 
     435           18 : fn resolved_column(ids: &ResolvedColumnIds) -> ResolvedColumnT {
     436           18 :     let (db, schema) = ids.catalog_schema_id.unpack_schema_id();
     437           18 :     let (table_id, column) = ids.catalog_table_column_id.unpack_table_column_id();
     438           18 :     build!(ResolvedColumnT {
     439           18 :         catalog_database_id: db,
     440           18 :         catalog_schema_id: schema,
     441           18 :         catalog_table_id: table_id.pack(),
     442           18 :         column_id: column,
     443           18 :         referenced_catalog_version: ids.referenced_catalog_version,
     444              :     })
     445           18 : }
     446              : 
     447           59 : fn pack_expression(script: &AnalyzedScript, expr: &Expression) -> ExpressionT {
     448           59 :     let scanned = &script.parsed.scanned;
     449           59 :     let inner = match &expr.inner {
     450            0 :         ExpressionInner::None => ExpressionSubTypeT::NONE,
     451           27 :         ExpressionInner::ColumnRef(c) => {
     452           27 :             ExpressionSubTypeT::ColumnRefExpression(Box::new(build!(ColumnRefExpressionT {
     453           27 :                 ast_scope_root: c.ast_scope_root.unwrap_or(u32::MAX),
     454           27 :                 column_name: Some(Box::new(qualified_column_name(&c.column_name))),
     455           27 :                 resolved_column: c.resolved_ids.as_ref().map(|ids| Box::new(resolved_column(ids))),
     456              :             })))
     457              :         }
     458           25 :         ExpressionInner::Literal(l) => {
     459           25 :             let begin = (l.raw_value.offset as usize).min(scanned.text.len());
     460           25 :             let end = (begin + l.raw_value.length as usize).min(scanned.text.len());
     461           25 :             ExpressionSubTypeT::Literal(Box::new(build!(LiteralT {
     462           25 :                 literal_type: l.literal_type,
     463           25 :                 // Always written, even if empty
     464           25 :                 raw_value: Some(scanned.text[begin..end].to_string()),
     465           25 :             })))
     466              :         }
     467            1 :         ExpressionInner::Comparison(c) => ExpressionSubTypeT::Comparison(Box::new(build!(ComparisonT {
     468            1 :             func: c.func,
     469            1 :             left_child: c.left_expression_id,
     470            1 :             right_child: c.right_expression_id,
     471            1 :         }))),
     472            1 :         ExpressionInner::BinaryExpression(b) => {
     473            1 :             ExpressionSubTypeT::BinaryExpression(Box::new(build!(BinaryExpressionT {
     474            1 :                 func: b.func,
     475            1 :                 left_child: b.left_expression_id,
     476            1 :                 right_child: b.right_expression_id,
     477            1 :             })))
     478              :         }
     479            4 :         ExpressionInner::FunctionCall(f) => {
     480            4 :             let arguments = match f.arguments {
     481            1 :                 FunctionArguments::None => None,
     482            3 :                 FunctionArguments::Generic { begin, count } => Some(
     483            3 :                     script.function_arguments[begin as usize..(begin + count) as usize]
     484            3 :                         .iter()
     485            3 :                         .filter_map(|a| a.expression_id)
     486            3 :                         .collect(),
     487              :                 ),
     488              :             };
     489            4 :             ExpressionSubTypeT::FunctionCallExpression(Box::new(build!(FunctionCallExpressionT {
     490            4 :                 func_name: match &f.function_name {
     491            4 :                     FunctionName::Qualified(n) => Some(Box::new(qualified_function_name(n))),
     492            0 :                     FunctionName::Known(_) => None,
     493              :                 },
     494            4 :                 func_call_modifiers: f.function_call_modifiers,
     495              :                 arguments,
     496              :             })))
     497              :         }
     498            1 :         ExpressionInner::ConstIntervalCast(c) => {
     499            1 :             let mut out = build!(ConstIntervalCastT { value_expression: c.value_expression_id });
     500            1 :             if let Some(interval) = &c.interval {
     501            1 :                 out.interval_type = interval.interval_type;
     502            1 :                 if let Some(p) = interval.precision_expression {
     503            0 :                     out.interval_precision = p as u32;
     504            1 :                 }
     505            0 :             }
     506            1 :             ExpressionSubTypeT::ConstIntervalCast(Box::new(out))
     507              :         }
     508              :     };
     509           59 :     build!(ExpressionT {
     510           59 :         ast_node_id: expr.ast_node_id,
     511           59 :         ast_statement_id: expr.ast_statement_id.unwrap_or(PROTO_NULL_U32),
     512           59 :         symbol_span: expr.location.map(symbol_span),
     513              :         inner,
     514              :     })
     515           59 : }
     516              : 
     517              : /// The renderer-specific packing of a visualization spec: (vegalite json, markdown spec)
     518              : pub type VisualizationPacker = dyn Fn(
     519              :     &AnalyzedScript,
     520              :     &VisualizationSpec,
     521              : ) -> (
     522              :     Option<String>,
     523              :     Option<Box<dashql_buffers::visualization::MarkdownSpecT>>,
     524              : );
     525              : 
     526              : /// Pack an analyzed script. `visualize` packs the renderer-specific specs.
     527           20 : pub fn pack_analyzed_script(script: &AnalyzedScript, visualize: &VisualizationPacker) -> AnalyzedScriptT {
     528           20 :     let entry = &script.entry;
     529           20 :     let tables = entry.table_declarations.iter().map(pack_table).collect();
     530           20 :     let table_references = script
     531           20 :         .table_references
     532           20 :         .iter()
     533           21 :         .map(|r| {
     534           13 :             let relation = r.inner.as_ref().expect("relation expression");
     535           13 :             build!(TableReferenceT {
     536           13 :                 ast_node_id: r.ast_node_id,
     537           13 :                 ast_statement_id: r.ast_statement_id.unwrap_or(PROTO_NULL_U32),
     538           13 :                 symbol_span: r.location.map(symbol_span),
     539           13 :                 table_name: Some(Box::new(qualified_table_name(&relation.table_name))),
     540           13 :                 alias_name: r.alias.as_ref().map(|(a, _)| a.text.to_string()),
     541           13 :                 resolved_table: relation.resolved_table.as_ref().map(|t| {
     542            9 :                     let (db, schema) = t.catalog_schema_id.unpack_schema_id();
     543            9 :                     Box::new(build!(ResolvedTableT {
     544            9 :                         catalog_database_id: db,
     545            9 :                         catalog_schema_id: schema,
     546            9 :                         catalog_table_id: t.catalog_table_id.unpack_table_id().pack(),
     547            9 :                         referenced_catalog_version: t.referenced_catalog_version,
     548            9 :                         table_name: Some(Box::new(qualified_table_name(&t.table_name))),
     549              :                     }))
     550            9 :                 }),
     551           13 :                 role: if r.role == TableRole::Write { TableReferenceRole::WRITE } else { TableReferenceRole::READ },
     552              :             })
     553           13 :         })
     554           20 :         .collect();
     555           20 :     let insert_statements = script
     556           20 :         .insert_statements
     557           20 :         .iter()
     558           20 :         .map(|insert| {
     559            1 :             build!(InsertStatementT {
     560            1 :                 ast_node_id: insert.ast_node_id,
     561            1 :                 ast_statement_id: insert.ast_statement_id.unwrap_or(PROTO_NULL_U32),
     562            1 :                 target_table_reference_id: script.table_references[insert.target as usize].table_reference_id.object,
     563            1 :                 source_ast_node_id: insert.source_ast_node_id.unwrap_or(PROTO_NULL_U32),
     564            1 :                 default_values: insert.default_values,
     565            1 :                 returning: insert.returning,
     566              :                 target_columns: Some(
     567            1 :                     insert
     568            1 :                         .target_columns
     569            1 :                         .iter()
     570            2 :                         .map(|c| build!(InsertTargetColumnT {
     571            2 :                             ast_node_id: c.ast_node_id,
     572            2 :                             column_name: Some(c.column_name.text.to_string()),
     573            2 :                             resolved_column: c.resolved.and_then(|loc| {
     574            2 :                                 let table = script.table(loc.table)?;
     575            2 :                                 let ids = ResolvedColumnIds {
     576            2 :                                     catalog_schema_id: table.catalog_schema_id,
     577            2 :                                     catalog_table_column_id: table.table_columns[loc.column as usize].object_id,
     578            2 :                                     referenced_catalog_version: table.catalog_version,
     579            2 :                                 };
     580            2 :                                 Some(Box::new(resolved_column(&ids)))
     581            2 :                             }),
     582              :                         }))
     583            1 :                         .collect(),
     584              :                 ),
     585              :             })
     586            1 :         })
     587           20 :         .collect();
     588           59 :     let expressions = script.expressions.iter().map(|e| pack_expression(script, e)).collect();
     589              : 
     590              :     // Index the resolved table and column refs by catalog id, then by their own id
     591           20 :     let mut table_refs_by_id: Vec<IndexedTableReferenceT> = script
     592           20 :         .table_references
     593           20 :         .iter()
     594           20 :         .enumerate()
     595           21 :         .filter_map(|(i, r)| {
     596           13 :             let resolved = r.inner.as_ref()?.resolved_table.as_ref()?;
     597            9 :             let (db, schema) = resolved.catalog_schema_id.unpack_schema_id();
     598            9 :             Some(build!(IndexedTableReferenceT {
     599            9 :                 catalog_database_id: db,
     600            9 :                 catalog_schema_id: schema,
     601            9 :                 catalog_table_id: resolved.catalog_table_id.unpack_table_id().pack(),
     602            9 :                 table_reference_id: i as u32,
     603            9 :             }))
     604           13 :         })
     605           20 :         .collect();
     606           21 :     table_refs_by_id.sort_unstable_by_key(|r| {
     607            4 :         (r.catalog_database_id, r.catalog_schema_id, r.catalog_table_id, r.table_reference_id)
     608            4 :     });
     609           20 :     let mut column_refs_by_id: Vec<IndexedColumnReferenceT> = script
     610           20 :         .expressions
     611           20 :         .iter()
     612           20 :         .enumerate()
     613           59 :         .filter_map(|(i, e)| {
     614           59 :             let c = e.column_ref()?;
     615           27 :             if !c.resolved.is_resolved() {
     616           11 :                 return None;
     617           16 :             }
     618           16 :             let ids = c.resolved_ids.as_ref()?;
     619           16 :             let (db, schema) = ids.catalog_schema_id.unpack_schema_id();
     620           16 :             let (table_id, column) = ids.catalog_table_column_id.unpack_table_column_id();
     621           16 :             Some(build!(IndexedColumnReferenceT {
     622           16 :                 catalog_database_id: db,
     623           16 :                 catalog_schema_id: schema,
     624           16 :                 catalog_table_id: table_id.pack(),
     625           16 :                 table_column_id: column,
     626           16 :                 expression_id: i as u32,
     627           16 :             }))
     628           59 :         })
     629           20 :         .collect();
     630           33 :     column_refs_by_id.sort_unstable_by_key(|r| {
     631           24 :         (r.catalog_database_id, r.catalog_schema_id, r.catalog_table_id, r.table_column_id, r.expression_id)
     632           24 :     });
     633              : 
     634           20 :     let name_scopes = script
     635           20 :         .name_scopes
     636           20 :         .iter()
     637           20 :         .enumerate()
     638           30 :         .map(|(i, scope)| {
     639           30 :             build!(NameScopeT {
     640           30 :                 scope_id: i as u32,
     641           30 :                 ast_node_id: scope.ast_node_id,
     642           30 :                 ast_statement_id: scope.ast_statement_id.unwrap_or(PROTO_NULL_U32),
     643           30 :                 child_scopes: Some(scope.child_scopes.clone()),
     644           30 :                 expressions: Some(scope.expressions.clone()),
     645           30 :                 table_references: Some(scope.table_references.clone()),
     646              :             })
     647           30 :         })
     648           20 :         .collect();
     649           20 :     let constant_expressions = script
     650           20 :         .constant_expressions
     651           20 :         .iter()
     652           34 :         .map(|c| {
     653           29 :             let root = &script.expressions[c.root as usize];
     654           29 :             build!(ConstantExpressionT {
     655           29 :                 ast_node_id: root.ast_node_id,
     656           29 :                 ast_statement_id: root.ast_statement_id.unwrap_or(PROTO_NULL_U32),
     657           29 :                 symbol_span: symbol_span(root.location.unwrap_or_default()),
     658           29 :                 expression_id: root.expression_id,
     659              :             })
     660           29 :         })
     661           20 :         .collect();
     662           20 :     let parameters = script
     663           20 :         .parameters
     664           20 :         .iter()
     665           23 :         .map(|p| {
     666            4 :             build!(ParameterT {
     667            4 :                 ast_node_id: p.ast_node_id,
     668            4 :                 ast_statement_id: p.ast_statement_id.unwrap_or(PROTO_NULL_U32),
     669            4 :                 symbol_span: Some(symbol_span(p.location)),
     670            4 :                 kind: match p.kind {
     671            3 :                     ParameterKind::Scalar => PackedParameterKind::SCALAR,
     672            1 :                     ParameterKind::Table => PackedParameterKind::TABLE,
     673              :                 },
     674            4 :                 name: p.name.as_deref().map(str::to_string),
     675            4 :                 cast_type: p.cast_type.as_ref().map(|t| Box::new(pack_sql_type(t))),
     676              :             })
     677            4 :         })
     678           20 :         .collect();
     679           20 :     let client_function_calls = script
     680           20 :         .client_function_calls
     681           20 :         .iter()
     682           20 :         .map(|call| {
     683            0 :             build!(ClientFunctionCallT {
     684            0 :                 ast_node_id: call.ast_node_id,
     685            0 :                 ast_statement_id: call.ast_statement_id,
     686            0 :                 explain: call.explain,
     687            0 :                 namespace_name: Some(call.namespace.to_string()),
     688            0 :                 function_name: call.function.as_deref().map(str::to_string),
     689            0 :                 effect: match call.effect {
     690            0 :                     crate::catalog::clfn::ClientFunctionEffect::Read => PackedClientFunctionEffect::READ,
     691            0 :                     crate::catalog::clfn::ClientFunctionEffect::Write => PackedClientFunctionEffect::WRITE,
     692              :                 },
     693            0 :                 description: script.describe_client_function_call(call.ast_statement_id).map(|d| d.to_string()),
     694              :             })
     695            0 :         })
     696           20 :         .collect();
     697           20 :     let function_declarations = entry.function_declarations.iter().map(pack_function_declaration).collect();
     698           20 :     let visualization_specs = script
     699           20 :         .visualization_specs
     700           20 :         .iter()
     701           20 :         .map(|spec| {
     702            3 :             let (vegalite_spec, markdown_spec) = visualize(script, spec);
     703            3 :             build!(VisualizationSpecT {
     704            3 :                 ast_node_id: spec.ast_node_id,
     705            3 :                 ast_statement_id: spec.ast_statement_id.unwrap_or(PROTO_NULL_U32),
     706            3 :                 renderer: spec.renderer.clone(),
     707              :                 vegalite_spec,
     708              :                 markdown_spec,
     709            3 :                 dashboard: Some(Box::new(build!(DashboardSpecT {
     710            3 :                     row: spec.dashboard.row,
     711            3 :                     column: spec.dashboard.column,
     712            3 :                     width: spec.dashboard.width,
     713            3 :                     height: spec.dashboard.height,
     714            3 :                 }))),
     715            3 :                 table_spec: (spec.renderer.as_deref() == Some("table")).then(|| Box::new(TableSpecT::default())),
     716              :             })
     717            3 :         })
     718           20 :         .collect();
     719           20 :     let inferred_table_schemas = script
     720           20 :         .inferred_table_schemas
     721           20 :         .iter()
     722           20 :         .map(|schema| {
     723            4 :             build!(InferredTableSchemaT {
     724            4 :                 table_name: Some(Box::new(qualified_table_name(&schema.table_name))),
     725            4 :                 confidence: dashql_buffers::analyzer::InferenceConfidence(schema.confidence as u8),
     726              :                 columns: Some(
     727            4 :                     schema
     728            4 :                         .columns
     729            4 :                         .iter()
     730            7 :                         .map(|c| build!(InferredColumnT {
     731            7 :                             column_name: Some(c.column_name.text.to_string()),
     732            7 :                             confidence: dashql_buffers::analyzer::InferenceConfidence(c.confidence as u8),
     733            7 :                             candidate_table_names: non_empty(
     734            7 :                                 c.candidate_table_names.iter().map(qualified_table_name).collect()
     735              :                             ),
     736              :                         }))
     737            4 :                         .collect(),
     738              :                 ),
     739              :             })
     740            4 :         })
     741           20 :         .collect();
     742           20 :     let errors = script
     743           20 :         .errors
     744           20 :         .iter()
     745           20 :         .map(|e| {
     746            4 :             build!(AnalyzerErrorT {
     747            4 :                 error_type: e.error_type,
     748            4 :                 severity: e.severity,
     749            4 :                 ast_node_id: e.ast_node_id.unwrap_or(PROTO_NULL_U32),
     750            4 :                 symbol_span: e.symbol_span.map(symbol_span),
     751            4 :                 text_span: e.text_span.map(text_span),
     752            4 :                 message: Some(e.message.clone()),
     753              :             })
     754            4 :         })
     755           20 :         .collect();
     756           20 :     build!(AnalyzedScriptT {
     757           20 :         catalog_entry_id: entry.catalog_entry_id,
     758           20 :         errors: non_empty(errors),
     759           20 :         tables: Some(tables),
     760           20 :         table_references: Some(table_references),
     761           20 :         insert_statements: Some(insert_statements),
     762           20 :         resolved_table_references_by_id: Some(table_refs_by_id),
     763           20 :         expressions: Some(expressions),
     764           20 :         resolved_column_references_by_id: Some(column_refs_by_id),
     765           20 :         constant_expressions: Some(constant_expressions),
     766           20 :         name_scopes: Some(name_scopes),
     767           20 :         function_declarations: Some(function_declarations),
     768           20 :         visualization_specs: Some(visualization_specs),
     769           20 :         inferred_table_schemas: Some(inferred_table_schemas),
     770           20 :         parameters: non_empty(parameters),
     771           20 :         client_function_calls: non_empty(client_function_calls),
     772              :     })
     773           20 : }
     774              : 
     775              : /// Pack a SQL type
     776           21 : pub fn pack_sql_type(t: &SqlType) -> SqlTypeT {
     777           21 :     let opt = |v: &Option<u32>| v.map_or(-1, |v| v as i32);
     778           21 :     let mut out = SqlTypeT::default();
     779           21 :     out.array_dimensions = t.array_dimensions;
     780           21 :     out.sql = Some(t.to_string());
     781           21 :     out.kind = match &t.kind {
     782            1 :         SqlTypeKind::Bool => PackedSqlTypeKind::BOOL,
     783            1 :         SqlTypeKind::Int1 => PackedSqlTypeKind::INT1,
     784            1 :         SqlTypeKind::Int2 => PackedSqlTypeKind::INT2,
     785            1 :         SqlTypeKind::Int4 => PackedSqlTypeKind::INT4,
     786            1 :         SqlTypeKind::Int8 => PackedSqlTypeKind::INT8,
     787            1 :         SqlTypeKind::Float4 => PackedSqlTypeKind::FLOAT4,
     788            1 :         SqlTypeKind::Float8 => PackedSqlTypeKind::FLOAT8,
     789            3 :         SqlTypeKind::Numeric { precision, scale } => {
     790            3 :             out.precision = opt(precision);
     791            3 :             out.scale = opt(scale);
     792            3 :             PackedSqlTypeKind::NUMERIC
     793              :         }
     794            1 :         SqlTypeKind::Varchar { length } => {
     795            1 :             out.length = opt(length);
     796            1 :             PackedSqlTypeKind::VARCHAR
     797              :         }
     798            1 :         SqlTypeKind::Char { length } => {
     799            1 :             out.length = opt(length);
     800            1 :             PackedSqlTypeKind::CHAR
     801              :         }
     802            1 :         SqlTypeKind::Text => PackedSqlTypeKind::TEXT,
     803            1 :         SqlTypeKind::Bit { length, varying } => {
     804            1 :             out.length = opt(length);
     805            1 :             out.varying = *varying;
     806            1 :             PackedSqlTypeKind::BIT
     807              :         }
     808            1 :         SqlTypeKind::Date => PackedSqlTypeKind::DATE,
     809            1 :         SqlTypeKind::Time { precision, with_time_zone } => {
     810            1 :             out.precision = opt(precision);
     811            1 :             out.with_time_zone = *with_time_zone;
     812            1 :             PackedSqlTypeKind::TIME
     813              :         }
     814            1 :         SqlTypeKind::Timestamp { precision, with_time_zone } => {
     815            1 :             out.precision = opt(precision);
     816            1 :             out.with_time_zone = *with_time_zone;
     817            1 :             PackedSqlTypeKind::TIMESTAMP
     818              :         }
     819            2 :         SqlTypeKind::Interval { fields, precision } => {
     820            2 :             out.precision = opt(precision);
     821            2 :             out.interval_fields = fields.map_or(-1, |f| f.0 as i16);
     822            2 :             PackedSqlTypeKind::INTERVAL
     823              :         }
     824            2 :         SqlTypeKind::Generic { name, modifiers } => {
     825            2 :             out.name = Some(name.to_string());
     826            2 :             out.modifiers = non_empty(modifiers.iter().map(|m| m.to_string()).collect());
     827            2 :             PackedSqlTypeKind::GENERIC
     828              :         }
     829              :     };
     830           21 :     out
     831           21 : }
     832              : 
     833              : /// Pack a cursor
     834            3 : pub fn pack_cursor(cursor: &crate::cursor::ScriptCursor) -> dashql_buffers::cursor::ScriptCursorT {
     835              :     use crate::cursor::CursorContext;
     836              :     use dashql_buffers::cursor::{
     837              :         RelativeSymbolPosition, ScriptCursorColumnRefContextT, ScriptCursorContextT, ScriptCursorT,
     838              :         ScriptCursorTableRefContextT,
     839              :     };
     840            3 :     let mut out = ScriptCursorT::default();
     841            3 :     out.text_offset = cursor.text_offset as u32;
     842            3 :     match &cursor.scanner_location {
     843            3 :         Some(location) => {
     844            3 :             let target = &location.current;
     845            3 :             out.scanner_symbol_id = target.symbol_id;
     846            3 :             out.scanner_relative_position = target.relative_pos;
     847            3 :             out.scanner_symbol_offset = target.symbol.location.offset;
     848            3 :             out.scanner_symbol_kind = target.symbol.kind as u32;
     849            3 :             out.scanner_symbol_completable = crate::completion::is_symbol_kind_completable(target.symbol.kind);
     850            3 :         }
     851            0 :         None => {
     852            0 :             out.scanner_symbol_id = u32::MAX;
     853            0 :             out.scanner_relative_position = RelativeSymbolPosition::AFTER_SYMBOL;
     854            0 :             out.scanner_symbol_offset = 0;
     855            0 :             out.scanner_symbol_kind = 0;
     856            0 :             out.scanner_symbol_completable = false;
     857            0 :         }
     858              :     }
     859            3 :     out.statement_id = cursor.statement_id.unwrap_or(u32::MAX);
     860            3 :     out.ast_node_id = cursor.ast_node_id.unwrap_or(u32::MAX);
     861            3 :     out.ast_path_to_root = non_empty(cursor.ast_path_to_root.clone());
     862            3 :     out.name_scopes = non_empty(cursor.name_scopes.clone());
     863            3 :     out.context = match cursor.context {
     864            1 :         CursorContext::None => ScriptCursorContextT::NONE,
     865            1 :         CursorContext::TableRef { table_reference_id, .. } => {
     866            1 :             let mut ctx = ScriptCursorTableRefContextT::default();
     867            1 :             ctx.table_reference_id = table_reference_id;
     868            1 :             ScriptCursorContextT::ScriptCursorTableRefContext(Box::new(ctx))
     869              :         }
     870            1 :         CursorContext::ColumnRef { expression_id } => {
     871            1 :             let mut ctx = ScriptCursorColumnRefContextT::default();
     872            1 :             ctx.expression_id = expression_id;
     873            1 :             ScriptCursorContextT::ScriptCursorColumnRefContext(Box::new(ctx))
     874              :         }
     875              :         // A column reference without an expression
     876              :         CursorContext::AliasDot => {
     877            0 :             let mut ctx = ScriptCursorColumnRefContextT::default();
     878            0 :             ctx.expression_id = PROTO_NULL_U32;
     879            0 :             ScriptCursorContextT::ScriptCursorColumnRefContext(Box::new(ctx))
     880              :         }
     881              :     };
     882            3 :     out
     883            3 : }
     884              : 
     885              : /// Pack what a list shows about a candidate or a catalog object
     886          255 : fn pack_candidate_fields(fields: &[crate::completion::CandidateField]) -> Option<Vec<dashql_buffers::completion::CompletionCandidateFieldT>> {
     887          255 :     if fields.is_empty() {
     888          199 :         return None;
     889           56 :     }
     890           84 :     let pack = |field: &crate::completion::CandidateField| {
     891           84 :         let mut out = dashql_buffers::completion::CompletionCandidateFieldT::default();
     892           84 :         out.name = Some(field.name.clone());
     893           84 :         out.detail = field.detail.clone();
     894           84 :         out.description = field.description.clone();
     895           84 :         out
     896           84 :     };
     897           56 :     Some(fields.iter().map(pack).collect())
     898          255 : }
     899              : 
     900         4987 : fn pack_candidate_info(info: &crate::completion::CandidateInfo) -> Option<Box<dashql_buffers::completion::CompletionCandidateInfoT>> {
     901         4987 :     if *info == crate::completion::CandidateInfo::default() {
     902         4902 :         return None;
     903           85 :     }
     904           85 :     let mut out = dashql_buffers::completion::CompletionCandidateInfoT::default();
     905           85 :     out.detail = info.detail.clone();
     906           85 :     out.description = info.description.clone();
     907           85 :     out.parameters = pack_candidate_fields(&info.parameters);
     908           85 :     out.result_description = info.result_description.clone();
     909           85 :     out.columns = pack_candidate_fields(&info.columns);
     910           85 :     out.example = info.example.clone();
     911           85 :     out.facts = pack_candidate_fields(&info.facts);
     912           85 :     Some(Box::new(out))
     913         4987 : }
     914              : 
     915              : /// Pack a completion
     916          266 : pub fn pack_completion(
     917          266 :     completion: &crate::completion::Completion,
     918          266 :     upper_case_keywords: bool,
     919          266 : ) -> dashql_buffers::completion::CompletionT {
     920              :     use crate::completion::CandidateObjectRef;
     921              :     use crate::utils::quote_identifier;
     922              :     use dashql_buffers::completion::{
     923              :         CompletionCandidateObjectT, CompletionCandidateObjectType, CompletionCandidateT, CompletionT, CompletionTableParameter,
     924              :     };
     925              :     use crate::table_parameters::TableParameterSource;
     926              : 
     927          266 :     let candidates = completion
     928          266 :         .top_candidates
     929          266 :         .iter()
     930         4867 :         .map(|candidate| {
     931         4867 :             let upper_keyword = upper_case_keywords && candidate.keyword_symbol.is_some();
     932         4867 :             let display_text = if upper_keyword {
     933           84 :                 candidate.completion_text.to_ascii_uppercase()
     934              :             } else {
     935         4783 :                 candidate.completion_text.to_string()
     936              :             };
     937         4867 :             let mut completion_text = if upper_keyword || candidate.completion_text_is_verbatim {
     938          151 :                 display_text.clone()
     939              :             } else {
     940         4716 :                 quote_identifier(&candidate.completion_text)
     941              :             };
     942         4867 :             let mut is_function = false;
     943         4867 :             let catalog_objects = candidate
     944         4867 :                 .catalog_objects
     945         4867 :                 .iter()
     946         4867 :                 .map(|&co| {
     947          120 :                     let object = &completion.candidate_objects[co];
     948          120 :                     let mut o = CompletionCandidateObjectT::default();
     949          120 :                     o.object_type = CompletionCandidateObjectType(object.object.object_type() as u8);
     950          120 :                     o.candidate_tags = object.candidate_tags;
     951          120 :                     o.score = object.score;
     952          180 :                     o.qualified_name = Some(object.qualified_name.iter().map(|n| quote_identifier(n)).collect());
     953          120 :                     o.qualified_name_target_idx = object.qualified_name_target_idx as u32;
     954          120 :                     o.prefer_qualified = object.prefer_qualified;
     955          120 :                     o.info = pack_candidate_info(&object.info);
     956          120 :                     match object.object {
     957           12 :                         CandidateObjectRef::Database(id) => o.catalog_database_id = id.unpack_database_id(),
     958           27 :                         CandidateObjectRef::Schema(id) => {
     959           27 :                             let (db, schema) = id.unpack_schema_id();
     960           27 :                             o.catalog_database_id = db;
     961           27 :                             o.catalog_schema_id = schema;
     962           27 :                         }
     963           49 :                         CandidateObjectRef::Table(loc) => {
     964           49 :                             o.table_parameter = match completion.table_parameter_source(loc) {
     965            0 :                                 Some(TableParameterSource::Script(_)) => CompletionTableParameter::SCRIPT,
     966            0 :                                 Some(TableParameterSource::Attachment(_)) => CompletionTableParameter::ATTACHMENT,
     967           49 :                                 None => CompletionTableParameter::NONE,
     968              :                             };
     969           49 :                             if let Some(table) = completion.table(loc) {
     970           49 :                                 let (db, schema) = table.catalog_schema_id.unpack_schema_id();
     971           49 :                                 o.catalog_database_id = db;
     972           49 :                                 o.catalog_schema_id = schema;
     973           49 :                                 o.catalog_table_id = table.table_id().pack();
     974           49 :                                 o.referenced_catalog_version = table.catalog_version;
     975           49 :                             }
     976              :                         }
     977           27 :                         CandidateObjectRef::Column(loc) => {
     978           27 :                             if let Some(table) = completion.table(loc.table) {
     979           27 :                                 let (db, schema) = table.catalog_schema_id.unpack_schema_id();
     980           27 :                                 let (table_id, column) =
     981           27 :                                     table.table_columns[loc.column as usize].object_id.unpack_table_column_id();
     982           27 :                                 o.catalog_database_id = db;
     983           27 :                                 o.catalog_schema_id = schema;
     984           27 :                                 o.catalog_table_id = table_id.pack();
     985           27 :                                 o.table_column_id = column;
     986           27 :                                 o.referenced_catalog_version = table.catalog_version;
     987           27 :                             }
     988              :                         }
     989            5 :                         CandidateObjectRef::Function(loc) => {
     990            5 :                             is_function = true;
     991            5 :                             if let Some(f) = completion
     992            5 :                                 .entry_data(loc.entry)
     993            5 :                                 .and_then(|d| d.function_declarations.get(loc.index as usize))
     994            5 :                             {
     995            5 :                                 let (db, schema) = f.catalog_schema_id.unpack_schema_id();
     996            5 :                                 o.catalog_database_id = db;
     997            5 :                                 o.catalog_schema_id = schema;
     998            5 :                             }
     999              :                         }
    1000              :                     }
    1001          120 :                     o
    1002          120 :                 })
    1003         4867 :                 .collect();
    1004         4867 :             if is_function {
    1005            5 :                 completion_text.push_str("()");
    1006         4862 :             }
    1007         4867 :             let mut c = CompletionCandidateT::default();
    1008         4867 :             c.completion_cursor_offset = (completion_text.len() - if is_function { 1 } else { 0 }) as u32;
    1009         4867 :             c.display_text = Some(display_text);
    1010         4867 :             c.completion_text = Some(completion_text);
    1011         4867 :             c.candidate_tags = candidate.candidate_tags;
    1012         4867 :             c.name_tags = candidate.coarse_name_tags;
    1013         4867 :             c.catalog_objects = Some(catalog_objects);
    1014         4867 :             c.info = pack_candidate_info(&candidate.info);
    1015         4867 :             c.score = candidate.score;
    1016         4867 :             c.target_location = Some(symbol_span(candidate.target_location));
    1017         4867 :             c.target_location_qualified = Some(symbol_span(candidate.target_location_qualified));
    1018         4867 :             c.keyword_continuation = candidate.keyword_continuation.filter(|k| !k.is_empty()).map(|k| {
    1019          856 :                 if upper_case_keywords {
    1020           35 :                     k.to_ascii_uppercase()
    1021              :                 } else {
    1022          821 :                     k.to_string()
    1023              :                 }
    1024          856 :             });
    1025         4867 :             c
    1026         4867 :         })
    1027          266 :         .collect();
    1028          266 :     let mut out = CompletionT::default();
    1029          266 :     out.cursor_offset = completion.cursor.text_offset as u32;
    1030          266 :     out.dot_completion = completion.dot_completion;
    1031          266 :     out.client_function_name = completion.client_function_name;
    1032          266 :     out.strategy = completion.strategy;
    1033          266 :     out.candidates = Some(candidates);
    1034          266 :     out
    1035          266 : }
    1036              : 
    1037              : /// The text of a type that a connection named. Any text is written as a quoted name to parse, like `"numeric(10,2)"`.
    1038           11 : fn unquote_type(text: &str) -> String {
    1039           11 :     match text.strip_prefix('"').and_then(|t| t.strip_suffix('"')) {
    1040            0 :         Some(inner) if !inner.is_empty() => inner.replace("\"\"", "\""),
    1041           11 :         _ => text.to_string(),
    1042              :     }
    1043           11 : }
    1044              : 
    1045              : /// Pack the declarations of a catalog entry as descriptor
    1046            8 : pub fn pack_catalog_descriptor(entry: &CatalogEntryData) -> crate::error::Result<Vec<u8>> {
    1047            8 :     let mut descriptor = dashql_buffers::catalog::CatalogDescriptorT::default();
    1048              :     // Both vectors are always written, even when empty
    1049              :     // A descriptor has no script: it holds no locations, and its columns are in declaration order. The analyzer sorts
    1050              :     // the columns of CREATE TABLE by name, the AST nodes restore the order.
    1051            8 :     let tables: Vec<_> = entry
    1052            8 :         .table_declarations
    1053            8 :         .iter()
    1054            9 :         .map(|declaration| {
    1055            9 :             let mut table = pack_table(declaration);
    1056            9 :             table.location = None;
    1057            9 :             if let Some(columns) = table.table_columns.take() {
    1058            9 :                 let mut columns: Vec<_> = declaration.table_columns.iter().zip(columns).collect();
    1059              :                 // Derived columns have no node, they come first and an ALTER adds to them
    1060           12 :                 columns.sort_by_key(|(declared, _)| declared.ast_node_id.unwrap_or(0));
    1061            9 :                 table.table_columns = Some(
    1062            9 :                     columns
    1063            9 :                         .into_iter()
    1064           13 :                         .map(|(_, mut column)| {
    1065           13 :                             column.location = None;
    1066           13 :                             column.column_type = column.column_type.map(|t| unquote_type(&t));
    1067           13 :                             column
    1068           13 :                         })
    1069            9 :                         .collect(),
    1070              :                 );
    1071            0 :             }
    1072            9 :             table
    1073            9 :         })
    1074            8 :         .collect();
    1075            8 :     descriptor.tables = Some(tables);
    1076            8 :     let mut functions: Vec<_> = entry.function_declarations.iter().map(pack_function_declaration).collect();
    1077            8 :     for function in &mut functions {
    1078            1 :         function.location = None;
    1079            1 :     }
    1080            8 :     descriptor.function_declarations = Some(functions);
    1081            8 :     let mut builder = flatbuffers::FlatBufferBuilder::new();
    1082            8 :     let packed = descriptor.pack(&mut builder);
    1083            8 :     builder.finish(packed, None);
    1084            8 :     let bytes = builder.finished_data().to_vec();
    1085            8 :     crate::catalog::descriptor::validate_descriptor(&bytes)?;
    1086            8 :     Ok(bytes)
    1087            8 : }
        

Generated by: LCOV version 2.0-1