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