LCOV - code coverage report
Current view: top level - dashql-core/src - pack.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 95.8 % 765 733
Test Date: 2026-11-06 17:46:50

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

Generated by: LCOV version 2.0-1