LCOV - code coverage report
Current view: top level - dashql-core/src - completion.rs Coverage Total Hit
Test: DashQL d805a4b Lines: 97.0 % 1316 1277
Test Date: 2026-11-09 20:22:13

            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(&current.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              : }
        

Generated by: LCOV version 2.0-1