Line data Source code
1 : //! Completion at a script cursor.
2 : //!
3 : //! Completion collects candidates from the expected grammar symbols at the cursor (probed through
4 : //! the LR automaton), the name search indexes of the script and the catalog, and the name scopes
5 : //! around the cursor. Candidates are scored by tags and the top k are selected by score, ties are ranked
6 : //! by name.
7 :
8 : // Bison names some symbol kinds in mixed case (S_Op)
9 : #![allow(non_upper_case_globals)]
10 :
11 : use crate::analyzer::{AnalyzedScript, ColumnResolution, ReferencedTable, ScopeId, ScopeSource, SourceVisibility, TableRole};
12 : use crate::catalog::*;
13 : use crate::cursor::{CursorContext, NameComponentType, ScriptCursor, SymbolLocation};
14 : use crate::hash::HashMap;
15 : use crate::table_parameters::{TableParameterSource, TABLE_PARAMETERS_ENTRY_ID};
16 : use crate::utils::topk::TopKHeap;
17 : use crate::utils::{ci_cmp, ci_find, ci_starts_with};
18 : use dashql_buffers::analyzer::NameTag;
19 : use dashql_buffers::completion::{CandidateTag, CompletionStrategy};
20 : use dashql_buffers::cursor::RelativeSymbolPosition;
21 : use dashql_buffers::parser::{AttributeKey, NodeType, SymbolSpan};
22 : use dashql_parser::keywords::Keyword;
23 : use dashql_parser::parser::context::resolve_text_span;
24 : use dashql_parser::parser::probe::{self, PrefixSnapshot};
25 : use dashql_parser::scanner::Symbol;
26 : use dashql_parser::symbol_kind::*;
27 : use dashql_parser::SymbolKind;
28 : use std::cmp::Ordering;
29 : use std::sync::Arc;
30 :
31 : /// A score value
32 : pub type Score = u32;
33 :
34 : // Keyword prevalence modifiers: "se" should suggest "select" before "set"
35 : const KEYWORD_A: Score = 4;
36 : const KEYWORD_B: Score = 3;
37 : const KEYWORD_C: Score = 2;
38 : const KEYWORD_D: Score = 0;
39 :
40 : // Coarse base score of a registered name
41 : const NAME_TAG_IGNORE: Score = 0;
42 : const NAME_TAG_UNLIKELY: Score = 10;
43 : const NAME_TAG_LIKELY: Score = 20;
44 :
45 : // Fine-granular score modifiers
46 : const SUBSTRING_SCORE_MODIFIER: Score = 30;
47 : const PREFIX_SCORE_MODIFIER: Score = 5;
48 : const EXACT_MATCH_SCORE_MODIFIER: Score = 15;
49 : const KEYWORD_SUBSTRING_BONUS: Score = 20;
50 : const THROUGH_CATALOG_SCORE_MODIFIER: Score = 2;
51 : const RESOLVING_TABLE_SCORE_MODIFIER: Score = 5;
52 : const UNRESOLVED_PEER_SCORE_MODIFIER: Score = 1;
53 : const DOT_SCHEMA_SCORE_MODIFIER: Score = 2;
54 : const DOT_TABLE_SCORE_MODIFIER: Score = 2;
55 : const DOT_COLUMN_SCORE_MODIFIER: Score = 2;
56 : const IN_NAME_SCOPE_SCORE_MODIFIER: Score = 10;
57 : const IN_SAME_STATEMENT_SCORE_MODIFIER: Score = 1;
58 :
59 : // Suffix-probe modifiers
60 : const SUFFIX_INCOMPATIBLE_PENALTY: Score = 15;
61 : const SUFFIX_DEPTH_ONE_BONUS: Score = 0;
62 : const SUFFIX_DEPTH_MANY_BONUS: Score = 1;
63 :
64 : // Identity candidate scores
65 : const IDENTITY_SCORE_TABLE_REF: Score = 50;
66 : const IDENTITY_SCORE_TABLE_REF_ALIAS: Score = 50;
67 : const IDENTITY_SCORE_COLUMN_REF: Score = 59;
68 :
69 : /// Longer identity candidates are dropped (e.g. an unterminated delimited identifier)
70 : const MAX_IDENTITY_LENGTH: usize = 64;
71 :
72 : /// The candidate tags, a bitset of `CandidateTag`
73 : pub type CandidateTags = u32;
74 :
75 1530222 : fn has(tags: CandidateTags, tag: CandidateTag) -> bool {
76 1530222 : tags & tag.0 != 0
77 1530222 : }
78 :
79 : /// `computeCandidateScore`
80 76516 : fn compute_candidate_score(tags: CandidateTags) -> Score {
81 76516 : let mut score: Score = 0;
82 1300772 : let add = |score: &mut Score, tag: CandidateTag, value: Score| {
83 1300772 : if has(tags, tag) {
84 96525 : *score += value;
85 1204247 : }
86 1300772 : };
87 76516 : add(&mut score, CandidateTag::KEYWORD_D, KEYWORD_D);
88 76516 : add(&mut score, CandidateTag::KEYWORD_C, KEYWORD_C);
89 76516 : add(&mut score, CandidateTag::KEYWORD_B, KEYWORD_B);
90 76516 : add(&mut score, CandidateTag::KEYWORD_A, KEYWORD_A);
91 76516 : add(&mut score, CandidateTag::SUBSTRING_MATCH, SUBSTRING_SCORE_MODIFIER);
92 76516 : add(&mut score, CandidateTag::PREFIX_MATCH, PREFIX_SCORE_MODIFIER);
93 76516 : add(&mut score, CandidateTag::EXACT_MATCH, EXACT_MATCH_SCORE_MODIFIER);
94 76516 : if has(tags, CandidateTag::EXPECTED_PARSER_SYMBOL) && has(tags, CandidateTag::SUBSTRING_MATCH) {
95 3721 : score += KEYWORD_SUBSTRING_BONUS;
96 72795 : }
97 76516 : add(&mut score, CandidateTag::THROUGH_CATALOG, THROUGH_CATALOG_SCORE_MODIFIER);
98 76516 : add(&mut score, CandidateTag::RESOLVING_TABLE, RESOLVING_TABLE_SCORE_MODIFIER);
99 76516 : add(&mut score, CandidateTag::UNRESOLVED_PEER, UNRESOLVED_PEER_SCORE_MODIFIER);
100 76516 : add(&mut score, CandidateTag::DOT_RESOLUTION_TABLE, DOT_TABLE_SCORE_MODIFIER);
101 76516 : add(&mut score, CandidateTag::DOT_RESOLUTION_SCHEMA, DOT_SCHEMA_SCORE_MODIFIER);
102 76516 : add(&mut score, CandidateTag::DOT_RESOLUTION_COLUMN, DOT_COLUMN_SCORE_MODIFIER);
103 76516 : add(&mut score, CandidateTag::IN_NAME_SCOPE, IN_NAME_SCOPE_SCORE_MODIFIER);
104 76516 : add(&mut score, CandidateTag::IN_SAME_STATEMENT, IN_SAME_STATEMENT_SCORE_MODIFIER);
105 76516 : add(&mut score, CandidateTag::SUFFIX_DEPTH_ONE, SUFFIX_DEPTH_ONE_BONUS);
106 76516 : add(&mut score, CandidateTag::SUFFIX_DEPTH_MANY, SUFFIX_DEPTH_MANY_BONUS);
107 76516 : let penalty = if has(tags, CandidateTag::SUFFIX_INCOMPATIBLE) { SUFFIX_INCOMPATIBLE_PENALTY } else { 0 };
108 76516 : score.saturating_sub(penalty)
109 76516 : }
110 :
111 : /// The base scores of the name tags per strategy
112 446 : fn name_tag_scores(strategy: CompletionStrategy) -> [(NameTag, Score); 7] {
113 446 : match strategy {
114 119 : CompletionStrategy::TABLE_REF => [
115 119 : (NameTag::NONE, NAME_TAG_IGNORE),
116 119 : (NameTag::SCHEMA_NAME, NAME_TAG_LIKELY),
117 119 : (NameTag::DATABASE_NAME, NAME_TAG_LIKELY),
118 119 : (NameTag::TABLE_NAME, NAME_TAG_LIKELY),
119 119 : (NameTag::TABLE_ALIAS, NAME_TAG_UNLIKELY),
120 119 : (NameTag::COLUMN_NAME, NAME_TAG_UNLIKELY),
121 119 : (NameTag::FUNCTION_NAME, NAME_TAG_UNLIKELY),
122 119 : ],
123 108 : CompletionStrategy::COLUMN_REF => [
124 108 : (NameTag::NONE, NAME_TAG_IGNORE),
125 108 : (NameTag::SCHEMA_NAME, NAME_TAG_UNLIKELY),
126 108 : (NameTag::DATABASE_NAME, NAME_TAG_UNLIKELY),
127 108 : (NameTag::TABLE_NAME, NAME_TAG_UNLIKELY),
128 108 : (NameTag::TABLE_ALIAS, NAME_TAG_LIKELY),
129 108 : (NameTag::COLUMN_NAME, NAME_TAG_LIKELY),
130 108 : (NameTag::FUNCTION_NAME, NAME_TAG_UNLIKELY),
131 108 : ],
132 219 : _ => [
133 219 : (NameTag::NONE, NAME_TAG_IGNORE),
134 219 : (NameTag::SCHEMA_NAME, NAME_TAG_LIKELY),
135 219 : (NameTag::DATABASE_NAME, NAME_TAG_LIKELY),
136 219 : (NameTag::TABLE_NAME, NAME_TAG_LIKELY),
137 219 : (NameTag::TABLE_ALIAS, NAME_TAG_LIKELY),
138 219 : (NameTag::COLUMN_NAME, NAME_TAG_LIKELY),
139 219 : (NameTag::FUNCTION_NAME, NAME_TAG_LIKELY),
140 219 : ],
141 : }
142 446 : }
143 :
144 : /// The prevalence of a keyword
145 76133 : fn keyword_prevalence(keyword: SymbolKind) -> CandidateTag {
146 76133 : match keyword {
147 362 : S_SELECT | S_FROM | S_WHERE => CandidateTag::KEYWORD_A,
148 473 : S_AND | S_GROUP_P | S_ORDER => CandidateTag::KEYWORD_B,
149 : S_AS | S_ASC_P | S_BY | S_CASE | S_CAST | S_DESC_P | S_END_P | S_LIKE | S_LIMIT | S_OFFSET | S_OR | S_SET
150 1453 : | S_THEN | S_WHEN | S_WITH => CandidateTag::KEYWORD_C,
151 73845 : _ => CandidateTag::KEYWORD_D,
152 : }
153 76133 : }
154 :
155 : /// Tokens after which no passive keyword hints are shown
156 52 : fn suppress_passive_hint(kind: SymbolKind) -> bool {
157 52 : matches!(kind, S_SELECT | S_FROM | S_JOIN | S_WHERE | S_ON | S_AND | S_OR | S_BY)
158 52 : }
159 :
160 : /// The continuation score of a keyword
161 1039426 : fn keyword_continuation_score(kind: SymbolKind) -> u8 {
162 1039426 : match kind {
163 8820 : S_BY | S_AS | S_EQUALS_GREATER | S_ON | S_SELECT | S_INTO | S_TABLE | S_SET => 10,
164 : S_OUTER_P | S_ASC_P | S_DESC_P | S_NULLS_P | S_FIRST_P | S_LAST_P | S_UNBOUNDED | S_PRECEDING | S_CURRENT_P
165 24508 : | S_ROW | S_WITHIN | S_SETS => 6,
166 1006098 : _ => 0,
167 : }
168 1039426 : }
169 :
170 : /// Is a scanner symbol kind completable?
171 467 : pub fn is_symbol_kind_completable(kind: SymbolKind) -> bool {
172 422 : !matches!(
173 467 : kind,
174 : S_SCONST
175 : | S_DCONST
176 : | S_ICONST
177 : | S_FCONST
178 : | S_BCONST
179 : | S_XCONST
180 : | S_COMMA
181 : | S_LRB
182 : | S_RRB
183 : | S_LSB
184 : | S_RSB
185 : | S_SEMICOLON
186 : | S_COLON
187 : | S_PLUS
188 : | S_MINUS
189 : | S_STAR
190 : | S_DIVIDE
191 : | S_MODULO
192 : | S_QUESTION_MARK
193 : | S_PARAM
194 : | S_CIRCUMFLEX
195 : | S_LESS_THAN
196 : | S_GREATER_THAN
197 : | S_EQUALS
198 : | S_Op
199 : | S_EQUALS_GREATER
200 : | S_LESS_EQUALS
201 : | S_GREATER_EQUALS
202 : | S_NOT_EQUALS
203 : | S_DOT_STAR_LA
204 : )
205 467 : }
206 :
207 : /// The catalog object of a candidate
208 : #[derive(Clone, Copy, Debug, PartialEq, Eq)]
209 : pub enum CandidateObjectRef {
210 : /// A database, by its catalog id (script reference or catalog declaration)
211 : Database(QualifiedCatalogObjectId),
212 : /// A schema, by its catalog id
213 : Schema(QualifiedCatalogObjectId),
214 : Table(TableLoc),
215 : Column(ColumnLoc),
216 : Function(ObjectLoc),
217 : }
218 :
219 : impl CandidateObjectRef {
220 243 : pub fn object_type(&self) -> CatalogObjectType {
221 243 : match self {
222 23 : CandidateObjectRef::Database(_) => CatalogObjectType::DatabaseReference,
223 53 : CandidateObjectRef::Schema(_) => CatalogObjectType::SchemaReference,
224 106 : CandidateObjectRef::Table(_) => CatalogObjectType::TableDeclaration,
225 54 : CandidateObjectRef::Column(_) => CatalogObjectType::ColumnDeclaration,
226 7 : CandidateObjectRef::Function(_) => CatalogObjectType::FunctionDeclaration,
227 : }
228 243 : }
229 : }
230 :
231 : /// An entry of a table in the details of a candidate
232 : #[derive(Clone, Debug, Default, PartialEq, Eq)]
233 : pub struct CandidateField {
234 : /// The name of a parameter or column, or the label of a fact
235 : pub name: String,
236 : /// The type, or the value of a fact
237 : pub detail: Option<String>,
238 : /// The prose, it can have inline Markdown
239 : pub description: Option<String>,
240 : }
241 :
242 : /// What the pane of a list shows about a candidate or a catalog object
243 : #[derive(Clone, Debug, Default, PartialEq, Eq)]
244 : pub struct CandidateInfo {
245 : /// One line: a signature, a type, a table
246 : pub detail: Option<String>,
247 : /// The prose for the selected candidate. It can have inline Markdown: code, bold and links.
248 : pub description: Option<String>,
249 : /// What the candidate takes: the parameters of a function, the attributes of a schema
250 : pub parameters: Vec<CandidateField>,
251 : /// What a result holds, it can have inline Markdown
252 : pub result_description: Option<String>,
253 : /// The columns of a result or a table
254 : pub columns: Vec<CandidateField>,
255 : /// A call that shows how to write it
256 : pub example: Option<String>,
257 : /// Short facts: a label and a value
258 : pub facts: Vec<CandidateField>,
259 : }
260 :
261 : /// What a list shows about a function. Everything known about a function goes through here.
262 18 : fn describe_function(function: &FunctionDeclaration) -> CandidateInfo {
263 18 : let returns_any = function.return_type.is_empty() || function.return_type.eq_ignore_ascii_case("any");
264 : // A function without parameters that returns anything is one whose signature is unknown
265 18 : let signature = if function.params.is_empty() && returns_any {
266 2 : None
267 : } else {
268 17 : let params: Vec<String> = function.params.iter().map(|p| format!("{} {}", p.param_name.text, p.param_type)).collect();
269 16 : let params = params.join(", ");
270 16 : Some(if returns_any { format!("({params})") } else { format!("({params}) → {}", function.return_type) })
271 : };
272 18 : let detail = match (signature, function.is_aggregate) {
273 1 : (Some(signature), true) => Some(format!("{signature} · aggregate")),
274 1 : (None, true) => Some("aggregate".to_string()),
275 16 : (signature, false) => signature,
276 : };
277 18 : CandidateInfo { detail, ..Default::default() }
278 18 : }
279 :
280 : /// The columns that a list shows of a table. Top candidates pay for it on every keystroke, and a table can be wide.
281 : const MAX_DESCRIBED_COLUMNS: usize = 200;
282 :
283 : /// What a list shows about a table
284 88 : fn describe_table(table: &TableDeclaration) -> CandidateInfo {
285 88 : let count = match table.table_columns.len() {
286 0 : 0 => None,
287 59 : 1 => Some("1 column".to_string()),
288 29 : n => Some(format!("{n} columns")),
289 : };
290 88 : let detail = match (table.is_view, count) {
291 1 : (true, Some(count)) => Some(format!("view · {count}")),
292 0 : (true, None) => Some("view".to_string()),
293 87 : (false, count) => count,
294 : };
295 : // A declaration keeps its columns sorted by name, a list shows them as they are written
296 88 : let mut columns: Vec<&TableColumn> = table.table_columns.iter().collect();
297 3873 : columns.sort_by_key(|column| column.location.map(|location| location.offset));
298 88 : let columns = columns
299 88 : .into_iter()
300 88 : .take(MAX_DESCRIBED_COLUMNS)
301 88 : .map(|column| CandidateField {
302 329 : name: column.column_name.text.to_string(),
303 329 : detail: column.column_type.as_deref().map(str::to_string),
304 329 : description: None,
305 329 : })
306 88 : .collect();
307 88 : CandidateInfo { detail, columns, ..Default::default() }
308 88 : }
309 :
310 : /// What a list shows about a column: its type and its table
311 123 : fn describe_column(table: &TableDeclaration, column: &TableColumn) -> CandidateInfo {
312 123 : let table_name = Some(table.table_name.table_name.text.to_string()).filter(|name| !name.is_empty());
313 123 : let detail = match (column.column_type.as_deref(), table_name) {
314 120 : (Some(column_type), Some(table_name)) => Some(format!("{column_type} · {table_name}")),
315 0 : (Some(column_type), None) => Some(column_type.to_string()),
316 3 : (None, table_name) => table_name,
317 : };
318 123 : CandidateInfo { detail, ..Default::default() }
319 123 : }
320 :
321 : /// A catalog object of a candidate
322 : #[derive(Clone, Debug)]
323 : pub struct CandidateObject {
324 : /// The candidate (index into the candidate buffer)
325 : pub candidate: usize,
326 : /// The candidate tags of the object
327 : pub candidate_tags: CandidateTags,
328 : /// The qualified id of the catalog object
329 : pub catalog_object_id: QualifiedCatalogObjectId,
330 : /// The catalog object
331 : pub object: CandidateObjectRef,
332 : /// The score, computed from the candidate tags
333 : pub score: Score,
334 : /// The qualified name
335 : pub qualified_name: Vec<Arc<str>>,
336 : /// The index of the target name in the qualified name, 0 if the qualified name is empty
337 : pub qualified_name_target_idx: usize,
338 : /// Whether to prefer the qualified name, set for ambiguous names and aliased tables
339 : pub prefer_qualified: bool,
340 : /// What a list shows about the object, set on the objects of the top candidates
341 : pub info: CandidateInfo,
342 : }
343 :
344 : /// A completion candidate
345 : #[derive(Clone, Debug)]
346 : pub struct Candidate {
347 : /// The text to insert
348 : pub completion_text: Arc<str>,
349 : /// Identity candidates are emitted as typed, without quoting
350 : pub completion_text_is_verbatim: bool,
351 : /// The combined analyzer tags of the name
352 : pub coarse_name_tags: u8,
353 : /// The combined candidate tags
354 : pub candidate_tags: CandidateTags,
355 : /// The text range to replace
356 : pub target_location: SymbolSpan,
357 : /// The text range to replace with a qualified name
358 : pub target_location_qualified: SymbolSpan,
359 : /// The catalog objects (indices into the candidate objects)
360 : pub catalog_objects: Vec<usize>,
361 : /// The score, higher is better
362 : pub score: Score,
363 : /// The keyword symbol of keyword candidates
364 : pub keyword_symbol: Option<SymbolKind>,
365 : /// The keyword continuation (e.g. "by")
366 : pub keyword_continuation: Option<&'static str>,
367 : /// What a list shows about a candidate without catalog objects
368 : pub info: CandidateInfo,
369 : }
370 :
371 : impl Candidate {
372 76865 : pub(crate) fn new(
373 76865 : completion_text: Arc<str>,
374 76865 : name_tags: u8,
375 76865 : tags: CandidateTags,
376 76865 : location: SymbolSpan,
377 76865 : qualified: SymbolSpan,
378 76865 : ) -> Self {
379 76865 : Self {
380 76865 : completion_text,
381 76865 : completion_text_is_verbatim: false,
382 76865 : coarse_name_tags: name_tags,
383 76865 : candidate_tags: tags,
384 76865 : target_location: location,
385 76865 : target_location_qualified: qualified,
386 76865 : catalog_objects: Vec::new(),
387 76865 : score: 0,
388 76865 : keyword_symbol: None,
389 76865 : keyword_continuation: None,
390 76865 : info: CandidateInfo::default(),
391 76865 : }
392 76865 : }
393 : }
394 :
395 : /// The rank of candidates, greater is better: higher scores, then smaller names in case-insensitive order, then in
396 : /// byte order. Names are unique among the candidates of the name indexes, but a keyword or the identity candidate
397 : /// may spell the same; keywords rank before names, the rest by their tags.
398 151688 : fn candidate_cmp(l: &Candidate, r: &Candidate) -> Ordering {
399 151688 : l.score
400 151688 : .cmp(&r.score)
401 151688 : .then_with(|| ci_cmp(r.completion_text.as_bytes(), l.completion_text.as_bytes()))
402 151688 : .then_with(|| r.completion_text.cmp(&l.completion_text))
403 151688 : .then_with(|| l.keyword_symbol.is_some().cmp(&r.keyword_symbol.is_some()))
404 151688 : .then_with(|| r.keyword_symbol.cmp(&l.keyword_symbol))
405 151688 : .then_with(|| r.candidate_tags.cmp(&l.candidate_tags))
406 151688 : }
407 :
408 : type CandidateHeap = TopKHeap<Candidate, fn(&Candidate, &Candidate) -> Ordering>;
409 :
410 : /// A completion at a cursor
411 : pub struct Completion<'c, 'a> {
412 : /// The cursor to complete at
413 : pub cursor: &'c ScriptCursor<'a>,
414 : /// The completion strategy, derived from the cursor context
415 : pub strategy: CompletionStrategy,
416 : /// Is the target qualified?
417 : pub dot_completion: bool,
418 : /// Is the target a function, option or attribute of a client function call?
419 : pub client_function_name: bool,
420 : /// Is the cursor at a name definition?
421 : pub at_definition: bool,
422 : /// Is the cursor between symbols?
423 : pub between_symbols: bool,
424 : /// Is there a real token after the cursor?
425 : pub has_post_cursor_token: bool,
426 : /// The symbol being completed
427 : pub target_symbol: Option<SymbolLocation>,
428 : /// The collected candidates, one per name
429 : pub candidates: Vec<Candidate>,
430 : /// The catalog objects of the candidates
431 : pub candidate_objects: Vec<CandidateObject>,
432 : /// The candidate indexes by completion text
433 : candidates_by_name: HashMap<Arc<str>, usize>,
434 : /// The candidate object indexes by catalog object id
435 : candidate_objects_by_id: HashMap<QualifiedCatalogObjectId, usize>,
436 : /// The heap that selects the top candidates
437 : candidate_heap: CandidateHeap,
438 : /// The selected candidates, best first
439 : pub top_candidates: Vec<Candidate>,
440 : }
441 :
442 : /// A dot candidate
443 : struct DotCandidate {
444 : /// The name to complete
445 : name: Arc<str>,
446 : /// The candidate tags
447 : candidate_tags: CandidateTags,
448 : /// The analyzer tags of the name
449 : name_tags: u8,
450 : /// The qualified id of the catalog object
451 : object_id: QualifiedCatalogObjectId,
452 : /// The catalog object the name refers to
453 : object: CandidateObjectRef,
454 : }
455 :
456 : /// Trim double quotes at both ends
457 760 : fn trim_double_quotes(s: &[u8]) -> &[u8] {
458 766 : let begin = s.iter().position(|&c| c != b'"').unwrap_or(s.len());
459 760 : let end = s.iter().rposition(|&c| c != b'"').map(|e| e + 1).unwrap_or(begin).max(begin);
460 760 : &s[begin..end]
461 760 : }
462 :
463 : /// Lossless text of bytes that came from the script text at char boundaries
464 182 : fn text_arc(bytes: &[u8]) -> Arc<str> {
465 182 : Arc::from(String::from_utf8_lossy(bytes).as_ref())
466 182 : }
467 :
468 : /// The match tags of a name against a prefix, None if the prefix is not a substring
469 72405 : fn match_tags(name: &[u8], prefix: &[u8]) -> Option<CandidateTags> {
470 72405 : let pos = ci_find(name, prefix)?;
471 3791 : let mut tags = CandidateTag::SUBSTRING_MATCH.0;
472 3791 : if pos == 0 {
473 893 : tags |= CandidateTag::PREFIX_MATCH.0;
474 893 : if name.len() == prefix.len() {
475 29 : tags |= CandidateTag::EXACT_MATCH.0;
476 864 : }
477 2898 : }
478 3791 : Some(tags)
479 72405 : }
480 :
481 : impl<'c, 'a> Completion<'c, 'a> {
482 538 : fn new(cursor: &'c ScriptCursor<'a>, k: usize) -> Self {
483 538 : let strategy = match cursor.context {
484 22 : CursorContext::TableRef { at_alias: true, .. } => CompletionStrategy::TABLE_REF_ALIAS,
485 117 : CursorContext::TableRef { .. } => CompletionStrategy::TABLE_REF,
486 108 : CursorContext::ColumnRef { .. } | CursorContext::AliasDot => CompletionStrategy::COLUMN_REF,
487 291 : CursorContext::None => CompletionStrategy::DEFAULT,
488 : };
489 538 : Self {
490 538 : cursor,
491 538 : strategy,
492 538 : dot_completion: false,
493 538 : client_function_name: false,
494 538 : at_definition: false,
495 538 : between_symbols: false,
496 538 : has_post_cursor_token: false,
497 538 : target_symbol: None,
498 538 : candidates: Vec::new(),
499 538 : candidate_objects: Vec::new(),
500 538 : candidates_by_name: HashMap::default(),
501 538 : candidate_objects_by_id: HashMap::default(),
502 538 : candidate_heap: TopKHeap::new(k, candidate_cmp as fn(&Candidate, &Candidate) -> Ordering),
503 538 : top_candidates: Vec::new(),
504 538 : }
505 538 : }
506 :
507 3548 : pub(crate) fn symbols(&self) -> &'a [Symbol] {
508 3548 : &self.cursor.script.scanned.unwrap().symbols
509 3548 : }
510 :
511 1378 : fn text(&self) -> &'a str {
512 1378 : &self.cursor.script.scanned.unwrap().text
513 1378 : }
514 :
515 2721 : fn analyzed(&self) -> Option<&'a Arc<AnalyzedScript>> {
516 2721 : self.cursor.script.analyzed
517 2721 : }
518 :
519 : /// The catalog snapshot of the analysis
520 2620 : pub(crate) fn catalog(&self) -> &'a CatalogSnapshot {
521 2620 : self.cursor.script.catalog()
522 2620 : }
523 :
524 : /// The text at a text range, clamped to the script text
525 1378 : pub(crate) fn text_at(&self, offset: u32, length: u32) -> &'a [u8] {
526 1378 : let text = self.text().as_bytes();
527 1378 : let begin = (offset as usize).min(text.len());
528 1378 : let end = (begin + length as usize).min(text.len());
529 1378 : &text[begin..end]
530 1378 : }
531 :
532 : /// The location of a symbol as a text-based SymbolSpan
533 865 : fn symbol_location(symbol: &Symbol) -> SymbolSpan {
534 865 : SymbolSpan::new(symbol.location.offset, symbol.location.length)
535 865 : }
536 :
537 : /// The data of a catalog entry: the analyzed script itself or a catalog entry
538 1076 : pub fn entry_data(&self, entry: CatalogEntryId) -> Option<&'a CatalogEntryData> {
539 1076 : if let Some(analyzed) = self.analyzed() {
540 1076 : if analyzed.entry.catalog_entry_id == entry {
541 208 : return Some(&analyzed.entry);
542 868 : }
543 0 : }
544 868 : self.catalog().entry(entry).map(|e| e.data())
545 1076 : }
546 :
547 794 : pub fn table(&self, loc: TableLoc) -> Option<&'a TableDeclaration> {
548 794 : if loc.entry == TABLE_PARAMETERS_ENTRY_ID {
549 18 : return self.analyzed()?.table(loc);
550 776 : }
551 776 : self.entry_data(loc.entry)?.table_declarations.get(loc.index as usize)
552 794 : }
553 :
554 : /// What the table parameter of a table reads, None for the tables of the catalog
555 49 : pub fn table_parameter_source(&self, loc: TableLoc) -> Option<TableParameterSource> {
556 49 : self.analyzed()?.table_parameters.as_ref()?.source(loc)
557 49 : }
558 :
559 : /// Complete the name of a table parameter `:foo` in table position with the names of the scripts and attachments
560 : /// of the session that have columns. Returns false if the cursor is not at the name of a table parameter.
561 510 : fn complete_table_parameter(&mut self, location: &crate::cursor::LocationInfo) -> bool {
562 510 : let symbols = self.symbols();
563 510 : let offset = self.cursor.text_offset as u32;
564 510 : let ends_at = |s: &Symbol| s.location.offset + s.location.length == offset;
565 588 : let is_name = |kind: SymbolKind| kind == S_IDENT || kind == S_SCONST;
566 : // The colon and the name after it, if any
567 510 : let current = location.current;
568 510 : let (colon, name) = if current.symbol.kind == S_COLON && ends_at(¤t.symbol) {
569 1 : (current.symbol_id, None)
570 509 : } else if is_name(current.symbol.kind)
571 350 : && current.symbol_id > 0
572 288 : && current.relative_pos != RelativeSymbolPosition::AFTER_SYMBOL
573 275 : && current.relative_pos != RelativeSymbolPosition::BEFORE_SYMBOL
574 : {
575 275 : (current.symbol_id - 1, Some(current.symbol))
576 : } else {
577 228 : match location.previous {
578 228 : Some(p) if p.relative_pos == RelativeSymbolPosition::END_OF_SYMBOL && p.symbol.kind == S_COLON => (p.symbol_id, None),
579 228 : Some(p) if p.relative_pos == RelativeSymbolPosition::END_OF_SYMBOL && is_name(p.symbol.kind) && p.symbol_id > 0 => {
580 11 : (p.symbol_id - 1, Some(p.symbol))
581 : }
582 223 : _ => return false,
583 : }
584 : };
585 287 : let Some(colon_symbol) = symbols.get(colon as usize) else { return false };
586 287 : if colon_symbol.kind != S_COLON {
587 283 : return false;
588 4 : }
589 : // The name follows the colon right away
590 4 : if let Some(name) = name {
591 3 : if colon_symbol.location.offset + colon_symbol.location.length != name.location.offset {
592 0 : return false;
593 3 : }
594 1 : }
595 : // A table position takes a join after the parameter, `where x = :y` and `insert into :t` take none
596 4 : let expected = probe::parse_until_after(symbols, colon + 1, S_IDENT);
597 4 : if !expected.contains(&S_JOIN) {
598 1 : return false;
599 3 : }
600 3 : let Some(results) = self.analyzed().and_then(|a| a.table_parameters.clone()) else { return true };
601 3 : let (prefix, target) = match name {
602 2 : Some(name) => {
603 2 : let text = self.text_at(name.location.offset, offset.saturating_sub(name.location.offset));
604 4 : (trim_double_quotes(text).iter().copied().filter(|&c| c != b'\'').collect::<Vec<u8>>(), Self::symbol_location(&name))
605 : }
606 1 : None => (Vec::new(), SymbolSpan::new(offset, 0)),
607 : };
608 3 : self.strategy = CompletionStrategy::TABLE_REF;
609 11 : for (index, script) in results.entries.iter().enumerate() {
610 11 : if script.table.is_none() || script.name.is_empty() {
611 3 : continue;
612 8 : }
613 8 : let tags = if prefix.is_empty() {
614 3 : CandidateTag::NONE.0
615 : } else {
616 5 : match match_tags(script.name.as_bytes(), &prefix) {
617 4 : Some(t) => t,
618 1 : None => continue,
619 : }
620 : };
621 7 : let candidate = self.candidates.len();
622 7 : self.candidates.push(Candidate::new(script.name.clone(), NameTag::TABLE_NAME.0, tags, target, target));
623 7 : self.candidates_by_name.insert(script.name.clone(), candidate);
624 7 : let loc = TableLoc { entry: TABLE_PARAMETERS_ENTRY_ID, index: index as u32 };
625 7 : let object_id = QualifiedCatalogObjectId::table(CatalogTableId::new(TABLE_PARAMETERS_ENTRY_ID, index as u32));
626 7 : self.add_object(candidate, tags, object_id, CandidateObjectRef::Table(loc));
627 : }
628 3 : self.select_top_candidates();
629 3 : self.describe_top_candidates();
630 3 : true
631 510 : }
632 :
633 1049 : fn is_at_eof(&self, id: u32) -> bool {
634 1049 : id as usize >= self.symbols().len()
635 1049 : }
636 :
637 606 : fn next_symbol(&self, id: u32) -> u32 {
638 606 : (id + 1).min(self.symbols().len() as u32 - 1)
639 606 : }
640 :
641 : /// The id and data of a name index object
642 246 : fn index_object(&self, object: CatalogObjectRef) -> Option<(QualifiedCatalogObjectId, CandidateObjectRef)> {
643 : // A script keeps its own declarations, and the published version of it may have other locations
644 246 : let own = self.analyzed().is_some_and(|analyzed| analyzed.entry.catalog_entry_id == object.entry());
645 246 : if !own && self.catalog().is_absent_object(object) {
646 0 : return None;
647 246 : }
648 246 : let data = self.entry_data(object.entry())?;
649 246 : let id = data.object_id(object);
650 246 : let candidate_object = match object {
651 13 : CatalogObjectRef::Database(_) => CandidateObjectRef::Database(id),
652 3 : CatalogObjectRef::Schema(_) => CandidateObjectRef::Schema(id),
653 75 : CatalogObjectRef::Table(t) => CandidateObjectRef::Table(t),
654 137 : CatalogObjectRef::Column(c) => CandidateObjectRef::Column(c),
655 18 : CatalogObjectRef::Function(f) => CandidateObjectRef::Function(f),
656 : };
657 246 : Some((id, candidate_object))
658 246 : }
659 :
660 : /// Add a catalog object to a candidate
661 341 : fn add_object(
662 341 : &mut self,
663 341 : candidate: usize,
664 341 : tags: CandidateTags,
665 341 : object_id: QualifiedCatalogObjectId,
666 341 : object: CandidateObjectRef,
667 341 : ) {
668 341 : let co = self.candidate_objects.len();
669 341 : self.candidate_objects.push(CandidateObject {
670 341 : candidate,
671 341 : candidate_tags: tags,
672 341 : catalog_object_id: object_id,
673 341 : object,
674 341 : score: 0,
675 341 : qualified_name: Vec::new(),
676 341 : qualified_name_target_idx: 0,
677 341 : prefer_qualified: false,
678 341 : info: CandidateInfo::default(),
679 341 : });
680 341 : self.candidates[candidate].catalog_objects.push(co);
681 341 : self.candidate_objects_by_id.insert(object_id, co);
682 341 : }
683 :
684 : /// Compute the completion at a cursor
685 538 : pub fn compute(cursor: &'c ScriptCursor<'a>, k: usize) -> Self {
686 538 : let mut completion = Completion::new(cursor, k);
687 : // Inside comments, only comment tags and the content of `@vis` tags are completed
688 538 : if cursor.script.scanned.is_some_and(|scanned| crate::cursor::is_inside_comment(scanned, cursor.text_offset)) {
689 28 : crate::completion_vis::complete_in_comment(&mut completion, k);
690 28 : return completion;
691 510 : }
692 510 : let Some(location) = cursor.scanner_location else { return completion };
693 510 : completion.target_symbol = Some(location.current);
694 : // The name of a table parameter, the scripts of the session
695 510 : if completion.complete_table_parameter(&location) {
696 3 : return completion;
697 507 : }
698 : // A function, an option or an attribute of a client function call
699 507 : if crate::completion_clfn::complete_client_function_call(&mut completion, k) {
700 38 : return completion;
701 469 : }
702 469 : let mut previous_symbol = location.previous;
703 :
704 : // Between symbols, or at the very beginning
705 469 : if location.current.relative_pos == RelativeSymbolPosition::AFTER_SYMBOL
706 423 : || (location.current.relative_pos == RelativeSymbolPosition::BEFORE_SYMBOL && previous_symbol.is_none())
707 52 : {
708 52 : completion.between_symbols = true;
709 417 : }
710 469 : let between_symbols = completion.between_symbols;
711 :
712 469 : let use_previous_if_at_end =
713 35 : |completion: &mut Completion, previous: &mut Option<SymbolLocation>| match *previous {
714 35 : Some(p) if p.relative_pos == RelativeSymbolPosition::END_OF_SYMBOL => {
715 10 : completion.target_symbol = Some(p);
716 10 : *previous = None;
717 10 : true
718 : }
719 25 : _ => false,
720 35 : };
721 :
722 469 : let target = completion.target_symbol.unwrap();
723 469 : if !between_symbols && target.is_dot() {
724 28 : match target.relative_pos {
725 : RelativeSymbolPosition::AFTER_SYMBOL | RelativeSymbolPosition::END_OF_SYMBOL => {
726 27 : completion.dot_completion = true
727 : }
728 : // Don't complete the dot itself, and not at its begin either (see docs/design/core_quirks.md)
729 : _ => {
730 1 : if target.relative_pos == RelativeSymbolPosition::BEGIN_OF_SYMBOL {
731 1 : use_previous_if_at_end(&mut completion, &mut previous_symbol);
732 1 : }
733 1 : return completion;
734 : }
735 : }
736 441 : } else if !between_symbols && target.is_trailing_dot() {
737 8 : match target.relative_pos {
738 : RelativeSymbolPosition::AFTER_SYMBOL | RelativeSymbolPosition::END_OF_SYMBOL => {
739 8 : completion.dot_completion = true
740 : }
741 : RelativeSymbolPosition::BEGIN_OF_SYMBOL => {
742 0 : if !use_previous_if_at_end(&mut completion, &mut previous_symbol) {
743 0 : return completion;
744 0 : }
745 : }
746 0 : _ => return completion,
747 : }
748 433 : }
749 :
750 : // Skip completion for the current symbol, e.g. at a comma
751 468 : if !between_symbols && !is_symbol_kind_completable(completion.target_symbol.unwrap().symbol.kind) {
752 34 : if !use_previous_if_at_end(&mut completion, &mut previous_symbol) {
753 25 : return completion;
754 9 : }
755 9 : if !is_symbol_kind_completable(completion.target_symbol.unwrap().symbol.kind) {
756 0 : return completion;
757 9 : }
758 434 : }
759 443 : let target = completion.target_symbol.unwrap();
760 :
761 : // Find the expected symbols when not dot-completing, keep the snapshot for the suffix probes
762 443 : let mut expects_identifier = false;
763 443 : let mut expected_at_cursor = probe::ExpectedAtCursor::default();
764 443 : if !completion.dot_completion {
765 408 : let parse_until = if target.relative_pos == RelativeSymbolPosition::AFTER_SYMBOL
766 46 : && !completion.is_at_eof(target.symbol_id)
767 : {
768 46 : completion.next_symbol(target.symbol_id)
769 : } else {
770 362 : target.symbol_id
771 : };
772 408 : expected_at_cursor = probe::parse_until_with_snapshot(completion.symbols(), parse_until);
773 408 : expects_identifier = expected_at_cursor.expected.contains(&S_IDENT);
774 35 : }
775 :
776 : // Is the cursor at a definition position?
777 443 : if let Some(parsed) = cursor.script.parsed {
778 1308 : for &node_id in &cursor.ast_path_to_root {
779 1299 : if matches!(
780 1308 : parsed.nodes[node_id as usize].attribute_key(),
781 : AttributeKey::SQL_CREATE_TABLE_NAME
782 : | AttributeKey::SQL_CREATE_AS_NAME
783 : | AttributeKey::SQL_VIEW_NAME
784 : | AttributeKey::SQL_COLUMN_DEF_NAME
785 : | AttributeKey::SQL_RESULT_TARGET_NAME
786 : | AttributeKey::SQL_CTE_NAME
787 : | AttributeKey::SQL_WINDOW_DEF_NAME
788 : | AttributeKey::SQL_CREATE_FUNCTION_NAME
789 : | AttributeKey::SQL_FUNCTION_PARAM_NAME
790 : ) {
791 9 : completion.at_definition = true;
792 9 : break;
793 1299 : }
794 : }
795 0 : }
796 :
797 : // Is the previous symbol an inner dot and the cursor on the successor?
798 443 : if previous_symbol.is_some_and(|p| p.is_dot()) && expects_identifier {
799 1 : if matches!(
800 57 : target.relative_pos,
801 : RelativeSymbolPosition::END_OF_SYMBOL
802 : | RelativeSymbolPosition::BEGIN_OF_SYMBOL
803 : | RelativeSymbolPosition::MID_OF_SYMBOL
804 56 : ) {
805 56 : completion.dot_completion = true;
806 56 : }
807 386 : }
808 :
809 443 : completion.has_post_cursor_token = completion.compute_has_post_cursor_token();
810 :
811 443 : if completion.dot_completion {
812 91 : completion.find_candidates_for_name_path();
813 353 : } else if between_symbols {
814 : // Only expected keywords with an empty target, the passive inline hints
815 52 : if !suppress_passive_hint(target.symbol.kind) {
816 31 : completion
817 31 : .add_expected_keywords_as_candidates(&expected_at_cursor.expected, &expected_at_cursor.prefix);
818 32 : }
819 : } else {
820 300 : completion.add_expected_keywords_as_candidates(&expected_at_cursor.expected, &expected_at_cursor.prefix);
821 : // An identity candidate with the text of the user
822 300 : if expects_identifier {
823 183 : let sym = target.symbol;
824 183 : let mut symbol_text = completion.text_at(sym.location.offset, sym.location.length);
825 183 : if symbol_text.len() > MAX_IDENTITY_LENGTH {
826 1 : symbol_text = &[];
827 182 : }
828 183 : if !symbol_text.is_empty() {
829 182 : let identity_score = if completion.at_definition {
830 9 : Score::MAX
831 : } else {
832 173 : match completion.strategy {
833 63 : CompletionStrategy::TABLE_REF => IDENTITY_SCORE_TABLE_REF,
834 69 : CompletionStrategy::COLUMN_REF => IDENTITY_SCORE_COLUMN_REF,
835 22 : CompletionStrategy::TABLE_REF_ALIAS => IDENTITY_SCORE_TABLE_REF_ALIAS,
836 19 : _ => Score::MAX,
837 : }
838 : };
839 182 : let location = Self::symbol_location(&sym);
840 182 : let mut identity =
841 182 : Candidate::new(text_arc(symbol_text), 0, CandidateTag::IDENTITY.0, location, location);
842 182 : identity.completion_text_is_verbatim = true;
843 182 : identity.score = identity_score;
844 182 : completion.candidate_heap.insert(identity);
845 1 : }
846 117 : }
847 : // Search the name indexes when expecting an identifier that is not a definition or alias
848 300 : if expects_identifier
849 183 : && !completion.at_definition
850 174 : && completion.strategy != CompletionStrategy::TABLE_REF_ALIAS
851 152 : {
852 152 : completion.find_candidates_in_indexes();
853 152 : completion.find_candidates_in_scope();
854 152 : completion.promote_tables_and_peers_for_unresolved_columns();
855 203 : }
856 : }
857 443 : if !between_symbols {
858 391 : completion.promote_identifiers_in_scope();
859 391 : }
860 443 : completion.select_top_candidates();
861 443 : if !between_symbols {
862 391 : completion.qualify_top_candidates();
863 391 : completion.describe_top_candidates();
864 391 : }
865 443 : completion.derive_keyword_continuations(&expected_at_cursor.prefix);
866 443 : completion
867 538 : }
868 :
869 : /// Is there a real token after the feed point?
870 443 : fn compute_has_post_cursor_token(&self) -> bool {
871 443 : let Some(target) = self.target_symbol else { return false };
872 443 : let replace_target = matches!(
873 443 : target.relative_pos,
874 : RelativeSymbolPosition::BEGIN_OF_SYMBOL
875 : | RelativeSymbolPosition::MID_OF_SYMBOL
876 : | RelativeSymbolPosition::END_OF_SYMBOL
877 : );
878 443 : let mut post_id = target.symbol_id;
879 443 : if (target.relative_pos == RelativeSymbolPosition::AFTER_SYMBOL || replace_target)
880 437 : && !self.is_at_eof(target.symbol_id)
881 437 : {
882 437 : post_id = self.next_symbol(target.symbol_id);
883 437 : }
884 443 : if self.is_at_eof(post_id) {
885 0 : return false;
886 443 : }
887 443 : self.symbols()[post_id as usize].kind != S_YYEOF
888 443 : }
889 :
890 : /// The feed point and whether feeding replaces the target token
891 774 : fn feed_point(&self) -> (u32, bool) {
892 774 : let target = self.target_symbol.unwrap();
893 774 : let replace_target = matches!(
894 774 : target.relative_pos,
895 : RelativeSymbolPosition::BEGIN_OF_SYMBOL
896 : | RelativeSymbolPosition::MID_OF_SYMBOL
897 : | RelativeSymbolPosition::END_OF_SYMBOL
898 : );
899 774 : let mut feed_id = target.symbol_id;
900 774 : if target.relative_pos == RelativeSymbolPosition::AFTER_SYMBOL && !self.is_at_eof(target.symbol_id) {
901 71 : feed_id = self.next_symbol(target.symbol_id);
902 703 : }
903 774 : (feed_id, replace_target)
904 774 : }
905 :
906 : /// Add the expected keywords to the heap
907 331 : fn add_expected_keywords_as_candidates(&mut self, symbols: &[SymbolKind], prefix: &PrefixSnapshot) {
908 331 : let target = self.target_symbol.unwrap();
909 331 : let (feed_id, replace_target) = self.feed_point();
910 :
911 : // The match tags of a keyword against the typed text
912 331 : let typed_text: &[u8] = {
913 331 : let symbol_ofs = target.symbol.location.offset;
914 331 : let symbol_prefix = target.text_offset.max(symbol_ofs) - symbol_ofs;
915 331 : trim_double_quotes(self.text_at(symbol_ofs, symbol_prefix))
916 : };
917 76133 : let keyword_tags = |keyword: SymbolKind, text: &str| -> CandidateTags {
918 76133 : let mut tags = CandidateTag::EXPECTED_PARSER_SYMBOL.0 | keyword_prevalence(keyword).0;
919 76133 : match target.relative_pos {
920 : RelativeSymbolPosition::BEGIN_OF_SYMBOL
921 : | RelativeSymbolPosition::MID_OF_SYMBOL
922 : | RelativeSymbolPosition::END_OF_SYMBOL => {
923 73518 : let typed = typed_text;
924 73518 : if !typed.is_empty() {
925 72324 : if let Some(t) = match_tags(text.as_bytes(), typed) {
926 3721 : tags |= t;
927 68603 : }
928 1194 : }
929 73518 : tags
930 : }
931 2615 : _ => tags,
932 : }
933 76133 : };
934 :
935 331 : let target_loc = if self.between_symbols {
936 31 : SymbolSpan::new(self.cursor.text_offset as u32, 0)
937 : } else {
938 300 : Self::symbol_location(&target.symbol)
939 : };
940 331 : let inside_select = self.cursor.script.parsed.is_some_and(|parsed| {
941 331 : self.cursor
942 331 : .ast_path_to_root
943 331 : .iter()
944 849 : .any(|&n| parsed.nodes[n as usize].node_type() == NodeType::OBJECT_SQL_SELECT)
945 331 : });
946 :
947 : // CALL is only worth offering if client functions give it a meaning, it must not outrank CREATE otherwise
948 331 : let has_client_functions = !self.catalog().client_function_specs().is_empty();
949 93427 : let offers = |keyword: SymbolKind| keyword != S_CALL_P || has_client_functions;
950 :
951 : // Probe the keywords against the tokens after the cursor
952 331 : let mut probes = Vec::new();
953 331 : let mut probe_input_idx = Vec::new();
954 331 : if self.has_post_cursor_token {
955 47 : let mut kinds = Vec::new();
956 17035 : for (i, &s) in symbols.iter().enumerate() {
957 17035 : if offers(s) && Keyword::name_of(s).is_some_and(|n| !n.is_empty()) {
958 16396 : kinds.push(s);
959 16396 : probe_input_idx.push(i);
960 16396 : }
961 : }
962 47 : if !kinds.is_empty() {
963 47 : probes = probe::probe_suffix_batch(self.symbols(), feed_id, prefix, &kinds, replace_target);
964 47 : }
965 284 : }
966 331 : let mut next_probe = 0;
967 :
968 79101 : for (i, &expected) in symbols.iter().enumerate() {
969 79101 : let Some(name) = Keyword::name_of(expected).filter(|n| !n.is_empty()).filter(|_| offers(expected)) else { continue };
970 76133 : let mut tags = keyword_tags(expected, name);
971 : // A leading FROM is no proactive suggestion outside of a SELECT
972 76133 : if expected == S_FROM && !inside_select && !has(tags, CandidateTag::SUBSTRING_MATCH) {
973 1 : continue;
974 76132 : }
975 76132 : if next_probe < probes.len() && probe_input_idx[next_probe] == i {
976 16396 : let probe = probes[next_probe];
977 16396 : next_probe += 1;
978 16396 : if !probe.k1_compatible {
979 1724 : tags |= CandidateTag::SUFFIX_INCOMPATIBLE.0;
980 14672 : } else if probe.reached_eof || probe.depth_consumed >= 2 {
981 13933 : tags |= CandidateTag::SUFFIX_DEPTH_MANY.0;
982 14659 : } else if probe.depth_consumed == 1 {
983 739 : tags |= CandidateTag::SUFFIX_DEPTH_ONE.0;
984 739 : }
985 59736 : }
986 76132 : let mut candidate = Candidate::new(Arc::from(name), 0, tags, target_loc, target_loc);
987 76132 : candidate.score = compute_candidate_score(tags);
988 76132 : candidate.keyword_symbol = Some(expected);
989 76132 : self.candidate_heap.insert(candidate);
990 : }
991 331 : }
992 :
993 : /// The text typed before the cursor in the target symbol, without double quotes
994 423 : fn read_target_prefix(&self) -> &'a [u8] {
995 423 : let Some(target) = self.target_symbol else { return &[] };
996 423 : let symbol_ofs = target.symbol.location.offset;
997 423 : let symbol_len = target.symbol.location.length;
998 423 : let cursor = self.cursor.text_offset as u32;
999 423 : let safe_cursor = cursor.max(symbol_ofs).min(symbol_ofs + symbol_len);
1000 423 : let symbol_text = self.text_at(symbol_ofs, symbol_len);
1001 423 : let prefix_len = ((safe_cursor.max(symbol_ofs) - symbol_ofs) as usize).min(symbol_text.len());
1002 423 : trim_double_quotes(&symbol_text[..prefix_len])
1003 423 : }
1004 :
1005 : /// Find candidates in a name search index
1006 271 : fn find_candidates_in_index(&mut self, index: &NameSearchIndex, through_catalog: bool) {
1007 271 : let target = self.target_symbol.unwrap();
1008 271 : let symbol_ofs = target.symbol.location.offset;
1009 271 : let symbol_text = self.text_at(symbol_ofs, target.symbol.location.length);
1010 271 : let prefix_text = self.read_target_prefix();
1011 : // Fall back to the full word if the cursor prefix is empty
1012 271 : let search_text = if prefix_text.is_empty() { trim_double_quotes(symbol_text) } else { prefix_text };
1013 :
1014 442 : for name in index.search(search_text) {
1015 : // A script doesn't provide what the connection reported as gone
1016 439 : if through_catalog && !name.objects.is_empty() && name.objects.iter().all(|o| self.catalog().is_absent_object(*o)) {
1017 0 : continue;
1018 439 : }
1019 : // Skip the name under the cursor itself
1020 439 : if !through_catalog
1021 231 : && name.occurrences == 1
1022 215 : && target.text_offset >= name.location.offset
1023 212 : && target.text_offset <= name.location.offset + name.location.length
1024 : {
1025 172 : continue;
1026 267 : }
1027 267 : let mut tags = CandidateTag::NAME_INDEX.0;
1028 267 : if through_catalog {
1029 208 : tags |= CandidateTag::THROUGH_CATALOG.0;
1030 210 : }
1031 0 : if matches!(
1032 267 : target.relative_pos,
1033 : RelativeSymbolPosition::BEGIN_OF_SYMBOL
1034 : | RelativeSymbolPosition::MID_OF_SYMBOL
1035 : | RelativeSymbolPosition::END_OF_SYMBOL
1036 : ) {
1037 267 : tags |= CandidateTag::SUBSTRING_MATCH.0;
1038 267 : if ci_starts_with(name.text.as_bytes(), prefix_text) {
1039 199 : tags |= CandidateTag::PREFIX_MATCH.0;
1040 199 : if prefix_text.len() == name.text.len() {
1041 29 : tags |= CandidateTag::EXACT_MATCH.0;
1042 172 : }
1043 68 : }
1044 0 : }
1045 267 : let candidate = match self.candidates_by_name.get(&name.text) {
1046 38 : Some(&c) => {
1047 38 : let candidate = &mut self.candidates[c];
1048 38 : candidate.coarse_name_tags |= name.tags.0;
1049 38 : candidate.candidate_tags |= tags;
1050 38 : c
1051 : }
1052 : None => {
1053 229 : let location = Self::symbol_location(&target.symbol);
1054 229 : self.candidates.push(Candidate::new(name.text.clone(), name.tags.0, tags, location, location));
1055 229 : let c = self.candidates.len() - 1;
1056 229 : self.candidates_by_name.insert(name.text.clone(), c);
1057 229 : c
1058 : }
1059 : };
1060 274 : for &object in &name.objects {
1061 246 : let Some((object_id, candidate_object)) = self.index_object(object) else { continue };
1062 246 : if let Some(&co) = self.candidate_objects_by_id.get(&object_id) {
1063 14 : self.candidate_objects[co].candidate_tags |= tags;
1064 14 : continue;
1065 232 : }
1066 232 : self.add_object(candidate, tags, object_id, candidate_object);
1067 : }
1068 : }
1069 271 : }
1070 :
1071 : /// Find candidates in the indexes of the script and the catalog
1072 152 : fn find_candidates_in_indexes(&mut self) {
1073 152 : let Some(analyzed) = self.analyzed() else { return };
1074 152 : self.find_candidates_in_index(analyzed.name_search_index(), false);
1075 153 : for (_, entry) in self.catalog().entries_ranked() {
1076 : // The catalog may still hold an earlier version of the script
1077 121 : if entry.data().catalog_entry_id == analyzed.entry.catalog_entry_id {
1078 2 : continue;
1079 119 : }
1080 119 : self.find_candidates_in_index(entry.name_search_index(), true);
1081 : }
1082 152 : }
1083 :
1084 : /// Add or merge a candidate without catalog objects
1085 40 : fn add_local_candidate(
1086 40 : &mut self,
1087 40 : name: &Arc<str>,
1088 40 : name_tags: u8,
1089 40 : mut tags: CandidateTags,
1090 40 : prefix: &[u8],
1091 40 : location: SymbolSpan,
1092 40 : qualified: SymbolSpan,
1093 40 : ) {
1094 40 : if !prefix.is_empty() {
1095 18 : match match_tags(name.as_bytes(), prefix) {
1096 13 : Some(t) => tags |= t,
1097 5 : None => return,
1098 : }
1099 22 : }
1100 35 : match self.candidates_by_name.get(name) {
1101 11 : Some(&c) => {
1102 11 : let candidate = &mut self.candidates[c];
1103 11 : candidate.coarse_name_tags |= name_tags;
1104 11 : candidate.candidate_tags |= tags;
1105 11 : // A visible local relation shadows catalog objects with the same spelling
1106 11 : candidate.catalog_objects.clear();
1107 11 : candidate.target_location = location;
1108 11 : candidate.target_location_qualified = qualified;
1109 11 : }
1110 24 : None => {
1111 24 : self.candidates.push(Candidate::new(name.clone(), name_tags, tags, location, qualified));
1112 24 : self.candidates_by_name.insert(name.clone(), self.candidates.len() - 1);
1113 24 : }
1114 : }
1115 40 : }
1116 :
1117 : /// The output column names of a CTE or derived table source
1118 80 : fn derived_columns(analyzed: &AnalyzedScript, source: &ScopeSource) -> Vec<Arc<str>> {
1119 80 : let (child, aliases) = match &source.table {
1120 10 : ReferencedTable::Cte(loc) => {
1121 10 : let cte = analyzed.name_scopes[loc.scope as usize].cte_definitions.at(loc.index as usize);
1122 10 : let Some(child) = cte.child_scope else { return Vec::new() };
1123 10 : let aliases = if source.column_aliases.is_empty() { &cte.column_aliases } else { &source.column_aliases };
1124 10 : (child, aliases)
1125 : }
1126 4 : ReferencedTable::Subquery(child) => (*child, &source.column_aliases),
1127 66 : ReferencedTable::Table(_) | ReferencedTable::Unresolved(_) => return Vec::new(),
1128 : };
1129 14 : let outputs = &analyzed.name_scopes[child as usize].output_columns;
1130 14 : let mut names = Vec::new();
1131 23 : for (i, output) in outputs.iter().enumerate() {
1132 23 : let name = aliases.get(i).unwrap_or(&output.column_name);
1133 23 : if !name.text.is_empty() {
1134 23 : names.push(name.text.clone());
1135 23 : }
1136 : }
1137 14 : names
1138 80 : }
1139 :
1140 : /// Is a scope within another one?
1141 5 : fn is_scope_within(analyzed: &AnalyzedScript, scope: u32, ancestor: Option<u32>) -> bool {
1142 5 : let mut current = Some(scope);
1143 9 : while let Some(c) = current {
1144 7 : if Some(c) == ancestor {
1145 3 : return true;
1146 4 : }
1147 4 : current = analyzed.name_scopes[c as usize].parent_scope;
1148 : }
1149 2 : false
1150 5 : }
1151 :
1152 : /// Add CTEs and the columns of CTE sources in scope
1153 152 : fn find_candidates_in_scope(&mut self) {
1154 152 : let cursor = self.cursor;
1155 152 : let (Some(target), Some(parsed), Some(analyzed)) = (self.target_symbol, cursor.script.parsed, self.analyzed())
1156 : else {
1157 0 : return;
1158 : };
1159 152 : let prefix = self.read_target_prefix();
1160 152 : let location = Self::symbol_location(&target.symbol);
1161 152 : let tags = CandidateTag::NAME_INDEX.0 | CandidateTag::IN_NAME_SCOPE.0;
1162 :
1163 152 : if self.strategy == CompletionStrategy::TABLE_REF {
1164 : // Non-recursive CTEs are visible after their definition and not within themselves
1165 63 : let scanned = &parsed.scanned;
1166 68 : for &scope_id in &cursor.name_scopes {
1167 68 : let scope = &analyzed.name_scopes[scope_id as usize];
1168 68 : for (_, cte) in scope.cte_definitions.iter() {
1169 7 : let Some(&cte_node) = scope.cte_definition_nodes.get(&cte.cte_name.text) else { continue };
1170 7 : let cte_text = resolve_text_span(
1171 7 : &scanned.text,
1172 7 : &scanned.symbols,
1173 7 : *parsed.nodes[cte_node as usize].symbol_span(),
1174 : );
1175 7 : if !scope.ctes_recursive && cte_text.offset as usize >= cursor.text_offset {
1176 1 : continue;
1177 6 : }
1178 6 : if !scope.ctes_recursive && Self::is_scope_within(analyzed, cursor.name_scopes[0], cte.child_scope)
1179 : {
1180 3 : continue;
1181 3 : }
1182 3 : self.add_local_candidate(
1183 3 : &cte.cte_name.text,
1184 3 : NameTag::TABLE_NAME.0,
1185 3 : tags,
1186 3 : prefix,
1187 3 : location,
1188 3 : location,
1189 : );
1190 : }
1191 : }
1192 63 : return;
1193 89 : }
1194 89 : if self.strategy != CompletionStrategy::COLUMN_REF {
1195 19 : return;
1196 70 : }
1197 : // The columns of CTE and derived table sources, catalog columns come from the name index
1198 77 : for &scope_id in &cursor.name_scopes {
1199 77 : let scope = &analyzed.name_scopes[scope_id as usize];
1200 82 : for source in &scope.sources {
1201 75 : for name in Self::derived_columns(analyzed, source) {
1202 13 : self.add_local_candidate(&name, NameTag::COLUMN_NAME.0, tags, prefix, location, location);
1203 13 : }
1204 : }
1205 : }
1206 152 : }
1207 :
1208 : /// The name scopes around the cursor with the FROM items that the column refs of the cursor can see in them.
1209 : /// The innermost scope comes first, a correlation barrier hides all scopes above it.
1210 419 : fn visible_scopes(analyzed: &AnalyzedScript, name_scopes: &[ScopeId]) -> Vec<(ScopeId, SourceVisibility)> {
1211 419 : let mut visible = Vec::with_capacity(name_scopes.len());
1212 419 : let mut visibility = SourceVisibility::All;
1213 423 : for &scope_id in name_scopes {
1214 310 : visible.push((scope_id, visibility));
1215 310 : let scope = &analyzed.name_scopes[scope_id as usize];
1216 310 : if scope.column_correlation_barrier {
1217 4 : break;
1218 306 : }
1219 306 : visibility = scope.parent_visibility;
1220 : }
1221 419 : visible
1222 419 : }
1223 :
1224 : /// Promote names in the current name scopes
1225 391 : fn promote_identifiers_in_scope(&mut self) {
1226 391 : let cursor = self.cursor;
1227 391 : let Some(analyzed) = self.analyzed() else { return };
1228 391 : let in_insert_source =
1229 395 : self.cursor.name_scopes.iter().any(|&s| analyzed.name_scopes[s as usize].column_correlation_barrier);
1230 402 : let promote = |completion: &mut Self, object_id: QualifiedCatalogObjectId| {
1231 233 : if let Some(&co) = completion.candidate_objects_by_id.get(&object_id) {
1232 96 : let candidate = completion.candidate_objects[co].candidate;
1233 96 : completion.candidate_objects[co].candidate_tags |= CandidateTag::IN_NAME_SCOPE.0;
1234 96 : completion.candidates[candidate].candidate_tags |= CandidateTag::IN_NAME_SCOPE.0;
1235 139 : }
1236 233 : };
1237 395 : for (scope_id, visibility) in Self::visible_scopes(analyzed, &cursor.name_scopes) {
1238 281 : let scope = &analyzed.name_scopes[scope_id as usize];
1239 : // Columns of resolved table refs in scope
1240 281 : for &table_ref_id in &scope.table_references {
1241 272 : let table_ref = &analyzed.table_references[table_ref_id as usize];
1242 272 : if !visibility.sees_node(table_ref.ast_node_id) {
1243 9 : continue;
1244 263 : }
1245 263 : if in_insert_source && table_ref.role == TableRole::Write {
1246 0 : continue;
1247 263 : }
1248 263 : let Some(resolved) = table_ref.inner.as_ref().and_then(|r| r.resolved_table.as_ref()) else { continue };
1249 122 : let table_id = resolved.catalog_table_id.unpack_table_id();
1250 : // Only tables of the catalog, the script itself is not necessarily published
1251 122 : let Some(table) = self.catalog().table_by_id(table_id) else { continue };
1252 195 : for column in &table.table_columns {
1253 195 : promote(self, column.object_id);
1254 195 : }
1255 : }
1256 : // Resolved column refs in scope
1257 282 : for &expr in &scope.expressions {
1258 154 : let Some(column_ref) = analyzed.expressions[expr as usize].column_ref() else { continue };
1259 154 : let ColumnResolution::TableColumn(loc) = column_ref.resolved else { continue };
1260 : // The location refers to the entries the script was analyzed against, which the catalog
1261 : // may have replaced since
1262 38 : let Some(column) = analyzed.table(loc.table).and_then(|t| t.table_columns.get(loc.column as usize))
1263 : else {
1264 0 : continue;
1265 : };
1266 38 : promote(self, column.object_id);
1267 : }
1268 : }
1269 391 : }
1270 :
1271 : /// Promote tables that resolve unresolved columns, and their columns
1272 152 : fn promote_tables_and_peers_for_unresolved_columns(&mut self) {
1273 152 : let cursor = self.cursor;
1274 152 : let (Some(_), Some(analyzed)) = (cursor.statement_id, self.analyzed()) else {
1275 15 : return;
1276 : };
1277 137 : let mut columns = Vec::new();
1278 149 : for &scope_id in &cursor.name_scopes {
1279 149 : let scope = &analyzed.name_scopes[scope_id as usize];
1280 150 : for &expr in &scope.expressions {
1281 108 : let Some(column_ref) = analyzed.expressions[expr as usize].column_ref() else { continue };
1282 108 : if column_ref.resolved.is_resolved() {
1283 30 : continue;
1284 78 : }
1285 78 : columns.clear();
1286 78 : self.resolve_table_columns_with_catalog(
1287 78 : analyzed,
1288 78 : &column_ref.column_name.column_name.text,
1289 78 : &mut columns,
1290 : );
1291 85 : for &column in &columns {
1292 31 : let Some(table) = self.table(column.table) else { continue };
1293 31 : if let Some(&co) = self.candidate_objects_by_id.get(&table.object_id) {
1294 2 : let candidate = self.candidate_objects[co].candidate;
1295 2 : self.candidate_objects[co].candidate_tags |= CandidateTag::RESOLVING_TABLE.0;
1296 2 : self.candidates[candidate].candidate_tags |= CandidateTag::RESOLVING_TABLE.0;
1297 29 : }
1298 39 : for peer in &table.table_columns {
1299 39 : if let Some(&co) = self.candidate_objects_by_id.get(&peer.object_id) {
1300 31 : let candidate = self.candidate_objects[co].candidate;
1301 31 : self.candidate_objects[co].candidate_tags |= CandidateTag::UNRESOLVED_PEER.0;
1302 31 : self.candidates[candidate].candidate_tags |= CandidateTag::UNRESOLVED_PEER.0;
1303 32 : }
1304 : }
1305 : }
1306 : }
1307 : }
1308 152 : }
1309 :
1310 : /// The table columns with a name in the catalog entries by rank, then in the script
1311 78 : fn resolve_table_columns_with_catalog(&self, analyzed: &Arc<AnalyzedScript>, name: &str, out: &mut Vec<ColumnLoc>) {
1312 79 : for (_, entry) in self.catalog().entries_ranked() {
1313 56 : if entry.data().catalog_entry_id == analyzed.entry.catalog_entry_id {
1314 1 : continue;
1315 55 : }
1316 55 : entry.data().resolve_table_columns(name, out);
1317 : }
1318 78 : out.retain(|column| !self.catalog().is_absent_table(column.table));
1319 78 : analyzed.entry.resolve_table_columns(name, out);
1320 78 : }
1321 :
1322 : /// Tables of a schema in any database, the own script first
1323 36 : fn resolve_schema_tables(&self, analyzed: &AnalyzedScript, schema: &str, out: &mut Vec<(TableLoc, bool)>) {
1324 36 : let own = &analyzed.entry;
1325 36 : let lower = (Arc::<str>::from(schema), Arc::<str>::from(""));
1326 36 : for ((s, _), tables) in own.tables_by_unqualified_schema.range(lower..) {
1327 4 : if &**s != schema {
1328 2 : break;
1329 2 : }
1330 2 : out.extend(tables.iter().map(|&t| (own.table_loc(t), false)));
1331 : }
1332 36 : let lower = (Arc::<str>::from(schema), MIN_LAYER, 0);
1333 36 : for ((s, _, entry_id), _) in self.catalog().entries_by_schema().range(lower..) {
1334 10 : if &**s != schema {
1335 7 : break;
1336 3 : }
1337 3 : if *entry_id == own.catalog_entry_id {
1338 0 : continue;
1339 3 : }
1340 3 : let Some(other) = self.catalog().entry(*entry_id) else { continue };
1341 3 : let other = other.data();
1342 3 : let lower = (Arc::<str>::from(schema), Arc::<str>::from(""));
1343 5 : for ((s, _), tables) in other.tables_by_unqualified_schema.range(lower..) {
1344 5 : if &**s != schema {
1345 2 : break;
1346 3 : }
1347 5 : out.extend(tables.iter().map(|&t| (other.table_loc(t), true)).filter(|(loc, _)| !self.catalog().is_absent_table(*loc)));
1348 : }
1349 : }
1350 36 : }
1351 :
1352 : /// Tables of a qualified schema, the own script first
1353 17 : fn resolve_qualified_schema_tables(
1354 17 : &self,
1355 17 : analyzed: &AnalyzedScript,
1356 17 : database: &str,
1357 17 : schema: &str,
1358 17 : out: &mut Vec<(TableLoc, bool)>,
1359 17 : ) {
1360 17 : let own = &analyzed.entry;
1361 17 : let key = (Arc::<str>::from(schema), Arc::<str>::from(database));
1362 17 : if let Some(tables) = own.tables_by_unqualified_schema.get(&key) {
1363 1 : out.extend(tables.iter().map(|&t| (own.table_loc(t), false)));
1364 16 : }
1365 17 : let lower = (Arc::<str>::from(database), Arc::<str>::from(schema), MIN_LAYER, 0);
1366 17 : let upper = (Arc::<str>::from(database), Arc::<str>::from(schema), MAX_LAYER, CatalogEntryId::MAX);
1367 17 : for ((_, _, _, entry_id), _) in self.catalog().entries_by_qualified_schema().range(lower..=upper) {
1368 16 : if *entry_id == own.catalog_entry_id {
1369 0 : continue;
1370 16 : }
1371 16 : let Some(other) = self.catalog().entry(*entry_id) else { continue };
1372 16 : let other = other.data();
1373 16 : if let Some(tables) = other.tables_by_unqualified_schema.get(&key) {
1374 16 : out.extend(tables.iter().map(|&t| (other.table_loc(t), true)).filter(|(loc, _)| !self.catalog().is_absent_table(*loc)));
1375 0 : }
1376 : }
1377 17 : }
1378 :
1379 : /// Schemas of a database, the own script first
1380 36 : fn resolve_database_schemas(
1381 36 : &self,
1382 36 : analyzed: &AnalyzedScript,
1383 36 : database: &str,
1384 36 : ) -> Vec<(Arc<str>, QualifiedCatalogObjectId, bool)> {
1385 36 : let mut out = Vec::new();
1386 36 : let lower = (Arc::<str>::from(database), Arc::<str>::from(""));
1387 36 : for ((db, _), &i) in analyzed.entry.schemas_by_qualified_name.range(lower.clone()..) {
1388 2 : if &**db != database {
1389 0 : break;
1390 2 : }
1391 2 : let schema = &analyzed.entry.schema_references[i];
1392 2 : out.push((schema.schema_name.clone(), schema.object_id, false));
1393 : }
1394 36 : for ((db, _), schema) in self.catalog().schemas().range(lower..) {
1395 31 : if &**db != database {
1396 0 : break;
1397 31 : }
1398 31 : out.push((schema.schema_name.clone(), schema.object_id, true));
1399 : }
1400 36 : out
1401 36 : }
1402 :
1403 : /// Complete a name path after a dot
1404 91 : fn find_candidates_for_name_path(&mut self) {
1405 91 : let cursor = self.cursor;
1406 91 : let Some(analyzed) = self.analyzed() else { return };
1407 91 : let scanned = cursor.script.scanned.unwrap();
1408 91 : let target = self.target_symbol.unwrap();
1409 :
1410 : // A recovered `alias.` context without a column ref node: resolve the alias in the scopes
1411 91 : if let CursorContext::AliasDot = cursor.context {
1412 9 : let Some(location) = cursor.scanner_location else { return };
1413 9 : let Some(dot) = ScriptCursor::dot_symbol(&location, &target) else { return };
1414 9 : let (alias_begin, alias_end) =
1415 9 : crate::cursor::qualifier_before(&scanned.text, dot.symbol.location.offset as usize);
1416 9 : let alias_text = &scanned.text[alias_begin..alias_end];
1417 : // The qualifier is sealed, only the text after the dot is replaced
1418 9 : let replace_at = SymbolSpan::new(cursor.text_offset as u32, 0);
1419 9 : let qualified_at = SymbolSpan::new(alias_begin as u32, (cursor.text_offset - alias_begin) as u32);
1420 9 : let tags = CandidateTag::DOT_RESOLUTION_COLUMN.0 | CandidateTag::IN_NAME_SCOPE.0;
1421 9 : for &scope_id in &cursor.name_scopes {
1422 9 : let scope = &analyzed.name_scopes[scope_id as usize];
1423 9 : let Some(source) = scope.source(alias_text) else { continue };
1424 9 : match &source.table {
1425 7 : ReferencedTable::Table(loc) => {
1426 7 : let names: Vec<Arc<str>> = self
1427 7 : .table(*loc)
1428 7 : .map(|t| {
1429 7 : let names = t.table_columns.iter().map(|c| &c.column_name);
1430 7 : let names = source.column_aliases.iter().chain(names.skip(source.column_aliases.len()));
1431 14 : names.map(|n| n.text.clone()).collect()
1432 7 : })
1433 7 : .unwrap_or_default();
1434 14 : for name in names {
1435 14 : self.add_local_candidate(
1436 14 : &name,
1437 14 : NameTag::COLUMN_NAME.0,
1438 14 : tags,
1439 14 : &[],
1440 14 : replace_at,
1441 14 : qualified_at,
1442 14 : );
1443 14 : }
1444 : }
1445 : ReferencedTable::Cte(_) | ReferencedTable::Subquery(_) => {
1446 4 : for name in Self::derived_columns(analyzed, source) {
1447 4 : self.add_local_candidate(
1448 4 : &name,
1449 4 : NameTag::COLUMN_NAME.0,
1450 4 : tags,
1451 4 : &[],
1452 4 : replace_at,
1453 4 : qualified_at,
1454 4 : );
1455 4 : }
1456 : }
1457 0 : ReferencedTable::Unresolved(_) => {}
1458 : }
1459 9 : return;
1460 : }
1461 0 : return;
1462 82 : }
1463 :
1464 82 : let cursor_location = target.text_offset;
1465 82 : let mut name_path_loc = SymbolSpan::default();
1466 82 : let name_path = cursor.read_cursor_name_path(&mut name_path_loc);
1467 261 : let resolve = |span: SymbolSpan| resolve_text_span(&scanned.text, &scanned.symbols, span);
1468 :
1469 : // Count the names and the sealed names before the cursor
1470 82 : let mut name_count = 0;
1471 82 : let mut sealed = 0;
1472 82 : let mut last_text_prefix: &[u8] = &[];
1473 82 : let name_path_ts = resolve(name_path_loc);
1474 82 : let mut truncate_at = name_path_ts.offset + name_path_ts.length;
1475 180 : while name_count < name_path.len() {
1476 179 : let component = &name_path[name_count];
1477 179 : let comp_ts = resolve(component.loc);
1478 179 : if component.component_type == NameComponentType::TrailingDot {
1479 26 : truncate_at = comp_ts.offset + 1;
1480 26 : break;
1481 153 : }
1482 153 : if component.component_type != NameComponentType::Name {
1483 0 : truncate_at = comp_ts.offset;
1484 0 : break;
1485 153 : }
1486 153 : if comp_ts.offset + comp_ts.length < cursor_location {
1487 98 : sealed += 1;
1488 98 : } else {
1489 : // The cursor points into a name, complete the text left of the cursor
1490 55 : let last_text = self.text_at(comp_ts.offset, comp_ts.length);
1491 56 : let last_content = last_text.iter().position(|&c| c != b'"').unwrap_or(last_text.len());
1492 55 : let last_content_ofs = comp_ts.offset as usize + last_content;
1493 55 : let prefix_length = (cursor_location as usize).max(last_content_ofs) - last_content_ofs;
1494 55 : let end = (last_content + prefix_length).min(last_text.len());
1495 55 : last_text_prefix = &last_text[last_content..end];
1496 55 : truncate_at = comp_ts.offset;
1497 55 : break;
1498 : }
1499 98 : name_count += 1;
1500 : }
1501 82 : let name_path = &name_path[..name_count];
1502 :
1503 : // The text ranges to replace
1504 82 : let script_length = scanned.text.len() as u32;
1505 82 : let name_path_end = (name_path_ts.offset + name_path_ts.length).min(script_length);
1506 82 : let replace_text_at = SymbolSpan::new(truncate_at, name_path_end.max(truncate_at) - truncate_at);
1507 82 : let name_path_text_loc =
1508 82 : SymbolSpan::new(name_path_ts.offset, name_path_end.max(name_path_ts.offset) - name_path_ts.offset);
1509 82 : if name_path.is_empty() {
1510 1 : return;
1511 81 : }
1512 :
1513 98 : let name_text = |i: usize| -> &str { analyzed.name_text(name_path[i].name.unwrap()) };
1514 81 : let mut dot_candidates: Vec<DotCandidate> = Vec::new();
1515 81 : match cursor.context {
1516 53 : CursorContext::TableRef { .. } => match sealed {
1517 : 1 => {
1518 : // a._: a is a schema in the default database or a database
1519 36 : let a = name_text(0);
1520 36 : let mut tables = Vec::new();
1521 36 : self.resolve_schema_tables(analyzed, a, &mut tables);
1522 36 : for (loc, through_catalog) in tables {
1523 7 : let Some(table) = self.table(loc) else { continue };
1524 7 : let mut tags = CandidateTag::DOT_RESOLUTION_TABLE.0;
1525 7 : if through_catalog {
1526 5 : tags |= CandidateTag::THROUGH_CATALOG.0;
1527 5 : }
1528 7 : dot_candidates.push(DotCandidate {
1529 7 : name: table.table_name.table_name.text.clone(),
1530 7 : candidate_tags: tags,
1531 7 : name_tags: NameTag::TABLE_NAME.0,
1532 7 : object_id: table.object_id,
1533 7 : object: CandidateObjectRef::Table(loc),
1534 7 : });
1535 : }
1536 36 : for (name, object_id, through_catalog) in self.resolve_database_schemas(analyzed, a) {
1537 33 : let mut tags = CandidateTag::DOT_RESOLUTION_SCHEMA.0;
1538 33 : if through_catalog {
1539 31 : tags |= CandidateTag::THROUGH_CATALOG.0;
1540 31 : }
1541 33 : dot_candidates.push(DotCandidate {
1542 33 : name,
1543 33 : candidate_tags: tags,
1544 33 : name_tags: NameTag::SCHEMA_NAME.0,
1545 33 : object_id,
1546 33 : object: CandidateObjectRef::Schema(object_id),
1547 33 : });
1548 : }
1549 : }
1550 : 2 => {
1551 : // a.b._: a is a database, b a schema
1552 17 : let (a, b) = (name_text(0), name_text(1));
1553 17 : let mut tables = Vec::new();
1554 17 : self.resolve_qualified_schema_tables(analyzed, a, b, &mut tables);
1555 17 : for (loc, through_catalog) in tables {
1556 17 : let Some(table) = self.table(loc) else { continue };
1557 17 : let mut tags = CandidateTag::DOT_RESOLUTION_TABLE.0;
1558 17 : if through_catalog {
1559 16 : tags |= CandidateTag::THROUGH_CATALOG.0;
1560 17 : }
1561 17 : dot_candidates.push(DotCandidate {
1562 17 : name: table.table_name.table_name.text.clone(),
1563 17 : candidate_tags: tags,
1564 17 : name_tags: NameTag::TABLE_NAME.0,
1565 17 : object_id: table.object_id,
1566 17 : object: CandidateObjectRef::Table(loc),
1567 17 : });
1568 : }
1569 : }
1570 0 : _ => {}
1571 : },
1572 : CursorContext::ColumnRef { .. } | CursorContext::AliasDot => {
1573 28 : if sealed == 1 {
1574 : // a._: a might be a table alias
1575 28 : let a = name_text(0);
1576 29 : for (scope_id, visibility) in Self::visible_scopes(analyzed, &cursor.name_scopes) {
1577 29 : let scope = &analyzed.name_scopes[scope_id as usize];
1578 32 : if let Some(source) = scope.sources.iter().find(|s| visibility.sees(s) && s.name.as_deref() == Some(a)) {
1579 26 : match &source.table {
1580 22 : ReferencedTable::Table(loc) => {
1581 22 : if let Some(decl) = self.table(*loc) {
1582 22 : let through_catalog = decl.table_id().origin != analyzed.catalog_entry_id();
1583 45 : for (i, column) in decl.table_columns.iter().enumerate() {
1584 45 : let mut tags = CandidateTag::DOT_RESOLUTION_COLUMN.0;
1585 45 : if through_catalog {
1586 26 : tags |= CandidateTag::THROUGH_CATALOG.0;
1587 37 : }
1588 : // Column aliases rename the leading columns
1589 45 : let name = source.column_aliases.get(i).unwrap_or(&column.column_name);
1590 45 : dot_candidates.push(DotCandidate {
1591 45 : name: name.text.clone(),
1592 45 : candidate_tags: tags,
1593 45 : name_tags: NameTag::COLUMN_NAME.0,
1594 45 : object_id: column.object_id,
1595 45 : object: CandidateObjectRef::Column(ColumnLoc {
1596 45 : table: *loc,
1597 45 : column: i as u32,
1598 45 : }),
1599 45 : });
1600 : }
1601 0 : }
1602 : }
1603 : ReferencedTable::Cte(_) | ReferencedTable::Subquery(_) => {
1604 3 : let tags = CandidateTag::DOT_RESOLUTION_COLUMN.0 | CandidateTag::IN_NAME_SCOPE.0;
1605 6 : for name in Self::derived_columns(analyzed, source) {
1606 6 : self.add_local_candidate(
1607 6 : &name,
1608 6 : NameTag::COLUMN_NAME.0,
1609 6 : tags,
1610 6 : last_text_prefix,
1611 6 : replace_text_at,
1612 6 : name_path_text_loc,
1613 6 : );
1614 6 : }
1615 : }
1616 1 : ReferencedTable::Unresolved(_) => {}
1617 : }
1618 26 : break;
1619 3 : }
1620 : }
1621 0 : }
1622 : }
1623 0 : CursorContext::None => {}
1624 : }
1625 :
1626 104 : for mut dot_candidate in dot_candidates {
1627 102 : if let Some(&co) = self.candidate_objects_by_id.get(&dot_candidate.object_id) {
1628 : // Update the tags and the replacement target
1629 0 : self.candidate_objects[co].candidate_tags |= dot_candidate.candidate_tags;
1630 0 : let candidate = self.candidate_objects[co].candidate;
1631 0 : self.candidates[candidate].target_location = replace_text_at;
1632 0 : self.candidates[candidate].target_location_qualified = name_path_text_loc;
1633 0 : continue;
1634 102 : }
1635 102 : if !last_text_prefix.is_empty() {
1636 58 : if let Some(t) = match_tags(dot_candidate.name.as_bytes(), last_text_prefix) {
1637 53 : dot_candidate.candidate_tags |= t;
1638 53 : }
1639 44 : }
1640 102 : let candidate = match self.candidates_by_name.get(&dot_candidate.name) {
1641 1 : Some(&c) => {
1642 1 : let existing = &mut self.candidates[c];
1643 1 : existing.target_location = replace_text_at;
1644 1 : existing.target_location_qualified = name_path_text_loc;
1645 : // A name can be several things, e.g. a table and a schema
1646 1 : existing.coarse_name_tags |= dot_candidate.name_tags;
1647 1 : existing.candidate_tags |= dot_candidate.candidate_tags;
1648 1 : c
1649 : }
1650 : None => {
1651 101 : self.candidates.push(Candidate::new(
1652 101 : dot_candidate.name.clone(),
1653 101 : dot_candidate.name_tags,
1654 101 : dot_candidate.candidate_tags,
1655 101 : replace_text_at,
1656 101 : name_path_text_loc,
1657 : ));
1658 101 : let c = self.candidates.len() - 1;
1659 101 : self.candidates_by_name.insert(dot_candidate.name.clone(), c);
1660 101 : c
1661 : }
1662 : };
1663 102 : self.add_object(candidate, dot_candidate.candidate_tags, dot_candidate.object_id, dot_candidate.object);
1664 : }
1665 91 : }
1666 :
1667 : /// Score all candidates and select the top k
1668 446 : fn select_top_candidates(&mut self) {
1669 446 : let scores = name_tag_scores(self.strategy);
1670 : // Objects with higher scores first, then in the order they were found
1671 446 : let mut object_heap =
1672 446 : TopKHeap::new(24, |l: &(usize, Score), r: &(usize, Score)| l.1.cmp(&r.1).then(r.0.cmp(&l.0)));
1673 496 : for i in 0..self.candidates.len() {
1674 : // The base score is the maximum among the name tags
1675 361 : let tags = self.candidates[i].coarse_name_tags;
1676 361 : let mut base_score = 0;
1677 2527 : for (tag, tag_score) in scores {
1678 2527 : base_score = base_score.max(if tags & tag.0 != 0 { tag_score } else { 0 });
1679 : }
1680 : // The top catalog objects
1681 361 : object_heap.clear();
1682 363 : for &co in &self.candidates[i].catalog_objects {
1683 337 : let score = compute_candidate_score(self.candidate_objects[co].candidate_tags);
1684 337 : self.candidate_objects[co].score = score;
1685 337 : object_heap.insert((co, score));
1686 337 : }
1687 361 : let objects: Vec<(usize, Score)> = object_heap.finish().clone();
1688 361 : let candidate = &mut self.candidates[i];
1689 361 : candidate.catalog_objects = objects.iter().map(|&(co, _)| co).collect();
1690 : // The score of the last, i.e. the lowest, of the top objects (see docs/design/core_quirks.md)
1691 361 : let object_score = match objects.last() {
1692 314 : Some(&(_, score)) => score,
1693 47 : None => compute_candidate_score(candidate.candidate_tags),
1694 : };
1695 361 : candidate.score = base_score + object_score;
1696 361 : let candidate = candidate.clone();
1697 361 : self.candidate_heap.insert(candidate);
1698 : }
1699 446 : self.candidate_heap.finish();
1700 446 : self.top_candidates = self.candidate_heap.take_entries();
1701 446 : }
1702 :
1703 : /// The qualified name of a table
1704 256 : fn qualified_table_name(name: &QualifiedTableName) -> Vec<Arc<str>> {
1705 256 : let mut names = Vec::with_capacity(4);
1706 256 : if !name.database_name.text.is_empty() {
1707 51 : names.push(name.database_name.text.clone());
1708 51 : names.push(name.schema_name.text.clone());
1709 51 : names.push(name.table_name.text.clone());
1710 209 : } else if !name.schema_name.text.is_empty() {
1711 8 : names.push(name.schema_name.text.clone());
1712 8 : names.push(name.table_name.text.clone());
1713 197 : } else if !name.table_name.text.is_empty() {
1714 197 : names.push(name.table_name.text.clone());
1715 197 : }
1716 256 : names
1717 256 : }
1718 :
1719 : /// Derive what a list shows about the catalog objects of the top candidates
1720 394 : fn describe_top_candidates(&mut self) {
1721 5174 : for top in &self.top_candidates {
1722 5174 : for &co in &top.catalog_objects {
1723 277 : let info = match self.candidate_objects[co].object {
1724 18 : CandidateObjectRef::Function(loc) => self
1725 18 : .entry_data(loc.entry)
1726 18 : .and_then(|d| d.function_declarations.get(loc.index as usize))
1727 18 : .map(describe_function),
1728 88 : CandidateObjectRef::Table(loc) => self.table(loc).map(describe_table),
1729 123 : CandidateObjectRef::Column(loc) => self.table(loc.table).and_then(|table| {
1730 123 : table.table_columns.get(loc.column as usize).map(|column| describe_column(table, column))
1731 123 : }),
1732 48 : CandidateObjectRef::Database(_) | CandidateObjectRef::Schema(_) => None,
1733 : };
1734 277 : self.candidate_objects[co].info = info.unwrap_or_default();
1735 : }
1736 : }
1737 394 : }
1738 :
1739 : /// Derive qualified names of the top candidates
1740 391 : fn qualify_top_candidates(&mut self) {
1741 : // The column candidates by the table that defines them: (table id, candidate object)
1742 391 : let mut columns_by_table: Vec<(QualifiedCatalogObjectId, usize)> = Vec::new();
1743 5167 : for top in &self.top_candidates {
1744 5167 : let mut column_count_in_scope = 0;
1745 5167 : let mut table_count = 0;
1746 5167 : for &co in &top.catalog_objects {
1747 270 : let object = &self.candidate_objects[co];
1748 270 : let in_scope = has(object.candidate_tags, CandidateTag::IN_NAME_SCOPE);
1749 270 : match object.object {
1750 123 : CandidateObjectRef::Column(loc) => {
1751 123 : column_count_in_scope += in_scope as usize;
1752 123 : columns_by_table.push((
1753 123 : QualifiedCatalogObjectId::table(object.catalog_object_id.unpack_table_column_id().0),
1754 123 : co,
1755 123 : ));
1756 123 : let table = self.table(loc.table);
1757 123 : let qualified = match table {
1758 123 : Some(table) => {
1759 123 : let mut names = Self::qualified_table_name(&table.table_name);
1760 123 : names.push(table.table_columns[loc.column as usize].column_name.text.clone());
1761 123 : names
1762 : }
1763 0 : None => Vec::new(),
1764 : };
1765 123 : let object = &mut self.candidate_objects[co];
1766 123 : object.qualified_name_target_idx = qualified.len().saturating_sub(1);
1767 123 : object.qualified_name = qualified;
1768 : }
1769 81 : CandidateObjectRef::Table(loc) => {
1770 81 : table_count += 1;
1771 81 : let qualified =
1772 81 : self.table(loc).map(|t| Self::qualified_table_name(&t.table_name)).unwrap_or_default();
1773 81 : let object = &mut self.candidate_objects[co];
1774 81 : object.qualified_name_target_idx = qualified.len().saturating_sub(1);
1775 81 : object.qualified_name = qualified;
1776 : }
1777 18 : CandidateObjectRef::Function(loc) => {
1778 18 : let qualified = self
1779 18 : .entry_data(loc.entry)
1780 18 : .and_then(|d| d.function_declarations.get(loc.index as usize))
1781 18 : .map(|f| {
1782 18 : let name = &f.function_name;
1783 18 : let mut names = Vec::with_capacity(3);
1784 18 : if !name.database_name.text.is_empty() {
1785 3 : names.push(name.database_name.text.clone());
1786 3 : names.push(name.schema_name.text.clone());
1787 3 : names.push(name.function_name.text.clone());
1788 16 : } else if !name.schema_name.text.is_empty() {
1789 2 : names.push(name.schema_name.text.clone());
1790 2 : names.push(name.function_name.text.clone());
1791 13 : } else if !name.function_name.text.is_empty() {
1792 13 : names.push(name.function_name.text.clone());
1793 13 : }
1794 18 : names
1795 18 : })
1796 18 : .unwrap_or_default();
1797 18 : let object = &mut self.candidate_objects[co];
1798 18 : object.qualified_name_target_idx = qualified.len().saturating_sub(1);
1799 18 : object.qualified_name = qualified;
1800 : }
1801 48 : _ => {}
1802 : }
1803 : }
1804 5167 : let prefer_qualified_columns = column_count_in_scope > 1;
1805 5167 : let prefer_qualified_tables = table_count > 1;
1806 5167 : for &co in &top.catalog_objects {
1807 270 : let object = &mut self.candidate_objects[co];
1808 270 : match object.object {
1809 123 : CandidateObjectRef::Column(_) => object.prefer_qualified = prefer_qualified_columns,
1810 81 : CandidateObjectRef::Table(_) => object.prefer_qualified = prefer_qualified_tables,
1811 66 : _ => {}
1812 : }
1813 : }
1814 : }
1815 :
1816 : // Qualify column candidates with the aliases of the table refs in scope
1817 391 : let Some(analyzed) = self.analyzed() else { return };
1818 398 : for &scope_id in &self.cursor.name_scopes {
1819 284 : let scope = &analyzed.name_scopes[scope_id as usize];
1820 284 : for &table_ref_id in &scope.table_references {
1821 275 : let table_ref = &analyzed.table_references[table_ref_id as usize];
1822 275 : let Some(resolved) = table_ref.inner.as_ref().and_then(|r| r.resolved_table.as_ref()) else { continue };
1823 128 : let mut has_match = false;
1824 148 : for &(table_id, co) in &columns_by_table {
1825 132 : if table_id != resolved.catalog_table_id {
1826 26 : continue;
1827 106 : }
1828 106 : has_match = true;
1829 106 : let CandidateObjectRef::Column(loc) = self.candidate_objects[co].object else { continue };
1830 106 : let column_name = match self.table(loc.table) {
1831 106 : Some(t) => t.table_columns[loc.column as usize].column_name.text.clone(),
1832 0 : None => continue,
1833 : };
1834 106 : let object = &mut self.candidate_objects[co];
1835 106 : match &table_ref.alias {
1836 54 : Some((alias, _)) => {
1837 54 : object.qualified_name = vec![alias.text.clone(), column_name];
1838 54 : object.qualified_name_target_idx = 1;
1839 54 : object.prefer_qualified = true;
1840 54 : }
1841 52 : None => {
1842 52 : let mut names = Self::qualified_table_name(&resolved.table_name);
1843 52 : names.push(column_name);
1844 52 : object.qualified_name_target_idx = names.len() - 1;
1845 52 : object.qualified_name = names;
1846 52 : }
1847 : }
1848 : }
1849 128 : if has_match {
1850 123 : columns_by_table.retain(|&(table_id, _)| table_id != resolved.catalog_table_id);
1851 51 : }
1852 : }
1853 : }
1854 391 : }
1855 :
1856 : /// Derive keyword continuations of the top candidates
1857 443 : fn derive_keyword_continuations(&mut self, prefix: &PrefixSnapshot) {
1858 443 : let Some(target) = self.target_symbol else { return };
1859 443 : let symbols = self.symbols();
1860 443 : let target_id = if self.between_symbols && !self.is_at_eof(target.symbol_id) {
1861 52 : self.next_symbol(target.symbol_id)
1862 : } else {
1863 391 : target.symbol_id
1864 : };
1865 443 : let (feed_id, replace_target) = self.feed_point();
1866 :
1867 : // The single keyword, or the uniquely best scored continuation
1868 5486 : let find_continuation = |expected: &[SymbolKind]| -> Option<SymbolKind> {
1869 5486 : let mut best = None;
1870 5486 : let mut best_score = 0;
1871 5486 : let mut best_count = 0;
1872 5486 : let mut keyword_count = 0;
1873 5486 : let mut unique = None;
1874 1084473 : for &sym in expected {
1875 1084473 : if Keyword::symbol_text(sym).is_none_or(|t| t.is_empty()) {
1876 45047 : continue;
1877 1039426 : }
1878 1039426 : keyword_count += 1;
1879 1039426 : unique = Some(sym);
1880 1039426 : let score = keyword_continuation_score(sym);
1881 1039426 : if score > best_score {
1882 6404 : best_score = score;
1883 6404 : best = Some(sym);
1884 6404 : best_count = 1;
1885 1033022 : } else if score == best_score && score > 0 {
1886 5724 : best_count += 1;
1887 1027298 : }
1888 : }
1889 5486 : if keyword_count == 1 {
1890 610 : return unique;
1891 4876 : }
1892 4876 : if best_count == 1 {
1893 399 : return best;
1894 4477 : }
1895 4477 : None
1896 5486 : };
1897 : // Keep a continuation only if the tokens after the cursor still parse after it
1898 443 : let has_post_cursor_token = self.has_post_cursor_token;
1899 1090 : let fits_suffix = |candidate: SymbolKind, continuation: SymbolKind| -> bool {
1900 1009 : if !has_post_cursor_token {
1901 954 : return true;
1902 55 : }
1903 55 : let probe =
1904 55 : probe::probe_suffix_sequence(symbols, feed_id, prefix, &[candidate, continuation], replace_target);
1905 55 : probe.k1_compatible || probe.reached_eof
1906 1009 : };
1907 :
1908 443 : let mut ident_continuation: Option<Option<&'static str>> = None;
1909 5679 : for candidate in self.top_candidates.iter_mut() {
1910 5679 : match candidate.keyword_symbol {
1911 5228 : Some(keyword) => {
1912 5228 : let expected = probe::parse_until_after(symbols, target_id, keyword);
1913 5228 : let Some(continuation) = find_continuation(&expected) else { continue };
1914 992 : if !fits_suffix(keyword, continuation) {
1915 46 : continue;
1916 946 : }
1917 946 : candidate.keyword_continuation = Keyword::symbol_text(continuation);
1918 : }
1919 : None => {
1920 451 : let text = *ident_continuation.get_or_insert_with(|| {
1921 258 : let expected = probe::parse_until_after(symbols, target_id, S_IDENT);
1922 258 : find_continuation(&expected).filter(|&c| fits_suffix(S_IDENT, c)).and_then(Keyword::symbol_text)
1923 258 : });
1924 451 : if let Some(text) = text.filter(|t| !t.is_empty()) {
1925 12 : candidate.keyword_continuation = Some(text);
1926 439 : }
1927 : }
1928 : }
1929 : }
1930 443 : }
1931 : }
|