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