diff --git a/crates/squawk_ide/src/completion.rs b/crates/squawk_ide/src/completion.rs index 183ab181..313efcee 100644 --- a/crates/squawk_ide/src/completion.rs +++ b/crates/squawk_ide/src/completion.rs @@ -29,6 +29,10 @@ pub fn completion(file: &ast::SourceFile, offset: TextSize) -> Vec { select_completions(&file, select_clause, &token) } + CompletionContext::SelectClauses(select) => select_clauses_completions(&select), + CompletionContext::SelectExpr(select) => select_expr_completions(&file, &select, &token), + CompletionContext::LimitClause => limit_completions(&file, &token), + CompletionContext::OffsetClause => offset_completions(&file, &token), CompletionContext::DeleteClauses(delete) => { delete_clauses_completions(&file, &delete, &token) } @@ -44,17 +48,9 @@ fn select_completions( let binder = binder::bind(file); let mut completions = vec![]; let schema = schema_qualifier_at_token(token); - let functions = binder.all_symbols_by_kind(SymbolKind::Function, schema.as_ref()); let position = token.text_range().start(); - completions.extend(functions.into_iter().map(|name| CompletionItem { - label: format!("{name}()"), - kind: CompletionItemKind::Function, - detail: function_detail(&binder, file, name, &schema, position), - insert_text: None, - insert_text_format: None, - trigger_completion_after_insert: false, - sort_text: None, - })); + + completions.extend(function_completions(&binder, file, &schema, position)); let tables = binder.all_symbols_by_kind(SymbolKind::Table, schema.as_ref()); completions.extend(tables.into_iter().map(|name| CompletionItem { @@ -73,97 +69,346 @@ fn select_completions( if let Some(parent) = select_clause.syntax().parent() && let Some(select) = ast::Select::cast(parent) - && let Some(from_clause) = select.from_clause() { + if let Some(from_clause) = select.from_clause() { + completions.push(CompletionItem { + label: "*".to_string(), + kind: CompletionItemKind::Operator, + detail: None, + insert_text: None, + insert_text_format: None, + trigger_completion_after_insert: false, + sort_text: None, + }); + completions.extend(column_completions_from_clause(&binder, file, &from_clause)); + } else if schema.is_none() { + completions.extend(select_clauses_completions(&select)); + } + } + + completions +} + +fn select_clauses_completions(select: &ast::Select) -> Vec { + let mut completions = vec![]; + + if select.from_clause().is_none() { + completions.push(CompletionItem { + label: "from".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("from $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + if select.where_clause().is_none() { completions.push(CompletionItem { - label: "*".to_string(), - kind: CompletionItemKind::Operator, + label: "where".to_owned(), + kind: CompletionItemKind::Snippet, detail: None, + insert_text: Some("where $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + if select.group_by_clause().is_none() { + completions.push(CompletionItem { + label: "group by".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("group by $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + if select.having_clause().is_none() { + completions.push(CompletionItem { + label: "having".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("having $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + if select.order_by_clause().is_none() { + completions.push(CompletionItem { + label: "order by".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("order by $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + if select.limit_clause().is_none() { + completions.push(CompletionItem { + label: "limit".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("limit $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + if select.offset_clause().is_none() { + completions.push(CompletionItem { + label: "offset".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("offset $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + if select.fetch_clause().is_none() { + completions.push(CompletionItem { + label: "fetch".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some( + "fetch ${1|first,next|} $2 ${3|row,rows|} ${4|only,with ties|}".to_owned(), + ), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + if select.locking_clauses().next().is_none() { + completions.push(CompletionItem { + label: "for".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("for ${1|update,no key update,share,key share|} $2".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + if select.window_clause().is_none() { + completions.push(CompletionItem { + label: "window".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("window $1 as ($0)".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + } + + completions.push(CompletionItem { + label: "union".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("union $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + completions.push(CompletionItem { + label: "intersect".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("intersect $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + completions.push(CompletionItem { + label: "except".to_owned(), + kind: CompletionItemKind::Snippet, + detail: None, + insert_text: Some("except $0".to_owned()), + insert_text_format: Some(CompletionInsertTextFormat::Snippet), + trigger_completion_after_insert: true, + sort_text: None, + }); + + completions +} + +fn limit_completions(file: &ast::SourceFile, token: &SyntaxToken) -> Vec { + let binder = binder::bind(file); + let schema = schema_qualifier_at_token(token); + let position = token.text_range().start(); + + let mut completions = vec![CompletionItem { + label: "all".to_owned(), + kind: CompletionItemKind::Keyword, + detail: None, + insert_text: None, + insert_text_format: None, + trigger_completion_after_insert: false, + sort_text: None, + }]; + + completions.extend(function_completions(&binder, file, &schema, position)); + completions +} + +fn offset_completions(file: &ast::SourceFile, token: &SyntaxToken) -> Vec { + let binder = binder::bind(file); + let schema = schema_qualifier_at_token(token); + let position = token.text_range().start(); + + function_completions(&binder, file, &schema, position) +} + +fn select_expr_completions( + file: &ast::SourceFile, + select: &ast::Select, + token: &SyntaxToken, +) -> Vec { + let binder = binder::bind(file); + let mut completions = vec![]; + let schema = schema_qualifier_at_token(token); + let position = token.text_range().start(); + + completions.extend(function_completions(&binder, file, &schema, position)); + + if let Some(from_clause) = select.from_clause() { + for from_item in from_clause.from_items() { + if let Some(table_name) = table_name_from_from_item(&from_item) { + completions.push(CompletionItem { + label: table_name.to_string(), + kind: CompletionItemKind::Table, + detail: None, + insert_text: None, + insert_text_format: None, + trigger_completion_after_insert: false, + sort_text: None, + }); + } + } + + completions.extend(column_completions_from_clause(&binder, file, &from_clause)); + } + + completions +} + +fn function_completions( + binder: &binder::Binder, + file: &ast::SourceFile, + schema: &Option, + position: TextSize, +) -> Vec { + binder + .all_symbols_by_kind(SymbolKind::Function, schema.as_ref()) + .into_iter() + .map(|name| CompletionItem { + label: format!("{name}()"), + kind: CompletionItemKind::Function, + detail: function_detail(binder, file, name, schema, position), insert_text: None, insert_text_format: None, trigger_completion_after_insert: false, sort_text: None, - }); - for table_ptr in resolve::table_ptrs_from_clause(&binder, &from_clause) { - let table_node = table_ptr.to_node(file.syntax()); - match resolve::find_table_source(&table_node) { - Some(resolve::TableSource::CreateTable(create_table)) => { - let columns = - resolve::collect_table_columns(&binder, file.syntax(), &create_table); - completions.extend(columns.into_iter().filter_map(|column| { - let name = column.name()?; - let detail = column.ty().map(|t| t.syntax().text().to_string()); - Some(CompletionItem { - label: Name::from_node(&name).to_string(), - kind: CompletionItemKind::Column, - detail, - insert_text: None, - insert_text_format: None, - trigger_completion_after_insert: false, - sort_text: None, - }) - })); - } - Some(resolve::TableSource::WithTable(with_table)) => { - let columns = resolve::collect_with_table_columns_with_types(&with_table); - completions.extend(columns.into_iter().map(|(name, ty)| CompletionItem { - label: name.to_string(), - kind: CompletionItemKind::Column, - detail: ty.map(|t| t.to_string()), - insert_text: None, - insert_text_format: None, - trigger_completion_after_insert: false, - sort_text: None, - })); - } - Some(resolve::TableSource::CreateView(create_view)) => { - let columns = resolve::collect_view_columns_with_types(&create_view); - completions.extend(columns.into_iter().map(|(name, ty)| CompletionItem { - label: name.to_string(), - kind: CompletionItemKind::Column, - detail: ty.map(|t| t.to_string()), - insert_text: None, - insert_text_format: None, - trigger_completion_after_insert: false, - sort_text: None, - })); - } - Some(resolve::TableSource::CreateMaterializedView(create_materialized_view)) => { - let columns = resolve::collect_materialized_view_columns_with_types( - &create_materialized_view, - ); - completions.extend(columns.into_iter().map(|(name, ty)| CompletionItem { - label: name.to_string(), - kind: CompletionItemKind::Column, - detail: ty.map(|t| t.to_string()), - insert_text: None, - insert_text_format: None, - trigger_completion_after_insert: false, - sort_text: None, - })); - } - Some(resolve::TableSource::ParenSelect(paren_select)) => { - let columns = resolve::collect_paren_select_columns_with_types( - &binder, - file.syntax(), - &paren_select, - ); - completions.extend(columns.into_iter().map(|(name, ty)| CompletionItem { - label: name.to_string(), + }) + .collect() +} + +fn column_completions_from_clause( + binder: &binder::Binder, + file: &ast::SourceFile, + from_clause: &ast::FromClause, +) -> Vec { + let mut completions = vec![]; + for table_ptr in resolve::table_ptrs_from_clause(binder, from_clause) { + let table_node = table_ptr.to_node(file.syntax()); + match resolve::find_table_source(&table_node) { + Some(resolve::TableSource::CreateTable(create_table)) => { + let columns = resolve::collect_table_columns(binder, file.syntax(), &create_table); + completions.extend(columns.into_iter().filter_map(|column| { + let name = column.name()?; + let detail = column.ty().map(|t| t.syntax().text().to_string()); + Some(CompletionItem { + label: Name::from_node(&name).to_string(), kind: CompletionItemKind::Column, - detail: ty.map(|t| t.to_string()), + detail, insert_text: None, insert_text_format: None, trigger_completion_after_insert: false, sort_text: None, - })); - } - None => {} + }) + })); + } + Some(resolve::TableSource::WithTable(with_table)) => { + let columns = resolve::collect_with_table_columns_with_types(&with_table); + completions.extend(columns.into_iter().map(|(name, ty)| CompletionItem { + label: name.to_string(), + kind: CompletionItemKind::Column, + detail: ty.map(|t| t.to_string()), + insert_text: None, + insert_text_format: None, + trigger_completion_after_insert: false, + sort_text: None, + })); + } + Some(resolve::TableSource::CreateView(create_view)) => { + let columns = resolve::collect_view_columns_with_types(&create_view); + completions.extend(columns.into_iter().map(|(name, ty)| CompletionItem { + label: name.to_string(), + kind: CompletionItemKind::Column, + detail: ty.map(|t| t.to_string()), + insert_text: None, + insert_text_format: None, + trigger_completion_after_insert: false, + sort_text: None, + })); + } + Some(resolve::TableSource::CreateMaterializedView(create_materialized_view)) => { + let columns = resolve::collect_materialized_view_columns_with_types( + &create_materialized_view, + ); + completions.extend(columns.into_iter().map(|(name, ty)| CompletionItem { + label: name.to_string(), + kind: CompletionItemKind::Column, + detail: ty.map(|t| t.to_string()), + insert_text: None, + insert_text_format: None, + trigger_completion_after_insert: false, + sort_text: None, + })); } + Some(resolve::TableSource::ParenSelect(paren_select)) => { + let columns = resolve::collect_paren_select_columns_with_types( + binder, + file.syntax(), + &paren_select, + ); + completions.extend(columns.into_iter().map(|(name, ty)| CompletionItem { + label: name.to_string(), + kind: CompletionItemKind::Column, + detail: ty.map(|t| t.to_string()), + insert_text: None, + insert_text_format: None, + trigger_completion_after_insert: false, + sort_text: None, + })); + } + None => {} } } - - return completions; + completions } fn schema_completions(binder: &binder::Binder) -> Vec { @@ -242,7 +487,7 @@ fn delete_clauses_completions( if delete.using_clause().is_none() { completions.push(CompletionItem { label: "using".to_owned(), - kind: CompletionItemKind::Keyword, + kind: CompletionItemKind::Snippet, detail: None, insert_text: Some("using $0".to_owned()), insert_text_format: Some(CompletionInsertTextFormat::Snippet), @@ -254,7 +499,7 @@ fn delete_clauses_completions( if delete.where_clause().is_none() { completions.push(CompletionItem { label: "where".to_owned(), - kind: CompletionItemKind::Keyword, + kind: CompletionItemKind::Snippet, detail: None, insert_text: Some("where $0".to_owned()), insert_text_format: Some(CompletionInsertTextFormat::Snippet), @@ -266,7 +511,7 @@ fn delete_clauses_completions( if delete.returning_clause().is_none() { completions.push(CompletionItem { label: "returning".to_owned(), - kind: CompletionItemKind::Keyword, + kind: CompletionItemKind::Snippet, detail: None, insert_text: Some("returning $0".to_owned()), insert_text_format: Some(CompletionInsertTextFormat::Snippet), @@ -359,6 +604,18 @@ fn delete_expr_completions( completions } +fn table_name_from_from_item(from_item: &ast::FromItem) -> Option { + if let Some(alias) = from_item.alias() + && let Some(alias_name) = alias.name() + { + return Some(Name::from_node(&alias_name)); + } + if let Some(name_ref) = from_item.name_ref() { + return Some(Name::from_node(&name_ref)); + } + None +} + fn qualifier_at_token(token: &SyntaxToken) -> Option { let qualifier_token = if token.kind() == SyntaxKind::DOT { token.prev_token() @@ -381,6 +638,10 @@ enum CompletionContext { TableOnly, Default, SelectClause(ast::SelectClause), + SelectClauses(ast::Select), + SelectExpr(ast::Select), + LimitClause, + OffsetClause, DeleteClauses(ast::Delete), DeleteExpr(ast::Delete), } @@ -388,6 +649,11 @@ enum CompletionContext { fn completion_context(token: &SyntaxToken) -> CompletionContext { if let Some(node) = token.parent() { let mut inside_delete_clause = false; + let mut inside_from_item = false; + let mut inside_paren_expr = false; + let mut inside_select_expr_clause = false; + let mut inside_limit_clause = false; + let mut inside_offset_clause = false; for a in node.ancestors() { if ast::Truncate::can_cast(a.kind()) || ast::Table::can_cast(a.kind()) { return CompletionContext::TableOnly; @@ -398,6 +664,25 @@ fn completion_context(token: &SyntaxToken) -> CompletionContext { { inside_delete_clause = true; } + if ast::LimitClause::can_cast(a.kind()) { + inside_limit_clause = true; + } + if ast::OffsetClause::can_cast(a.kind()) { + inside_offset_clause = true; + } + if ast::WhereClause::can_cast(a.kind()) + || ast::GroupByClause::can_cast(a.kind()) + || ast::HavingClause::can_cast(a.kind()) + || ast::OrderByClause::can_cast(a.kind()) + { + inside_select_expr_clause = true; + } + if ast::FromItem::can_cast(a.kind()) { + inside_from_item = true; + } + if ast::ParenExpr::can_cast(a.kind()) { + inside_paren_expr = true; + } if let Some(delete) = ast::Delete::cast(a.clone()) { if inside_delete_clause { return CompletionContext::DeleteExpr(delete); @@ -407,6 +692,20 @@ fn completion_context(token: &SyntaxToken) -> CompletionContext { } return CompletionContext::TableOnly; } + if let Some(select) = ast::Select::cast(a.clone()) { + if inside_limit_clause { + return CompletionContext::LimitClause; + } + if inside_offset_clause { + return CompletionContext::OffsetClause; + } + if inside_select_expr_clause { + return CompletionContext::SelectExpr(select); + } + if inside_from_item && !inside_paren_expr && select.from_clause().is_some() { + return CompletionContext::SelectClauses(select); + } + } if let Some(select_clause) = ast::SelectClause::cast(a.clone()) { return CompletionContext::SelectClause(select_clause); } @@ -477,7 +776,7 @@ fn default_completions() -> Vec { ["delete from", "select", "table", "truncate"] .map(|stmt| CompletionItem { label: stmt.to_owned(), - kind: CompletionItemKind::Keyword, + kind: CompletionItemKind::Snippet, detail: None, insert_text: Some(format!("{stmt} $0;")), insert_text_format: Some(CompletionInsertTextFormat::Snippet), @@ -583,13 +882,12 @@ mod tests { item.label, format!("{:?}", item.kind), item.detail.unwrap_or_default(), - item.insert_text.unwrap_or_default(), ] }) .collect(); let mut builder = Builder::default(); - builder.push_record(["label", "kind", "detail", "insert_text"]); + builder.push_record(["label", "kind", "detail"]); for row in rows { builder.push_record(row); } @@ -602,12 +900,12 @@ mod tests { #[test] fn completion_at_start() { assert_snapshot!(completions("$0"), @r" - label | kind | detail | insert_text - -------------+---------+--------+----------------- - delete from | Keyword | | delete from $0; - select | Keyword | | select $0; - table | Keyword | | table $0; - truncate | Keyword | | truncate $0; + label | kind | detail + -------------+---------+-------- + delete from | Snippet | + select | Snippet | + table | Snippet | + truncate | Snippet | "); } @@ -617,12 +915,12 @@ mod tests { create table t(a int); $0 "), @r" - label | kind | detail | insert_text - -------------+---------+--------+----------------- - delete from | Keyword | | delete from $0; - select | Keyword | | select $0; - table | Keyword | | table $0; - truncate | Keyword | | truncate $0; + label | kind | detail + -------------+---------+-------- + delete from | Snippet | + select | Snippet | + table | Snippet | + truncate | Snippet | "); } @@ -642,38 +940,38 @@ $0 create table users (id int); truncate $0; "), @r" - label | kind | detail | insert_text - --------------------+--------+--------+------------- - users | Table | | - public | Schema | | - pg_catalog | Schema | | - pg_temp | Schema | | - pg_toast | Schema | | - information_schema | Schema | | + label | kind | detail + --------------------+--------+-------- + users | Table | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | "); } #[test] fn completion_table_at_top_level() { assert_snapshot!(completions("$0"), @r" - label | kind | detail | insert_text - -------------+---------+--------+----------------- - delete from | Keyword | | delete from $0; - select | Keyword | | select $0; - table | Keyword | | table $0; - truncate | Keyword | | truncate $0; + label | kind | detail + -------------+---------+-------- + delete from | Snippet | + select | Snippet | + table | Snippet | + truncate | Snippet | "); } #[test] fn completion_table_nested() { assert_snapshot!(completions("select * from ($0)"), @r" - label | kind | detail | insert_text - -------------+---------+--------+----------------- - delete from | Keyword | | delete from $0; - select | Keyword | | select $0; - table | Keyword | | table $0; - truncate | Keyword | | truncate $0; + label | kind | detail + -------------+---------+-------- + delete from | Snippet | + select | Snippet | + table | Snippet | + truncate | Snippet | "); } @@ -683,14 +981,44 @@ truncate $0; create table users (id int); table $0; "), @r" - label | kind | detail | insert_text - --------------------+--------+--------+------------- - users | Table | | - public | Schema | | - pg_catalog | Schema | | - pg_temp | Schema | | - pg_toast | Schema | | - information_schema | Schema | | + label | kind | detail + --------------------+--------+-------- + users | Table | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | + "); + } + + #[test] + fn completion_select_without_from() { + assert_snapshot!(completions(" +create table t (a int); +select $0; +"), @r" + label | kind | detail + --------------------+---------+-------- + except | Snippet | + fetch | Snippet | + for | Snippet | + from | Snippet | + group by | Snippet | + having | Snippet | + intersect | Snippet | + limit | Snippet | + offset | Snippet | + order by | Snippet | + t | Table | + union | Snippet | + where | Snippet | + window | Snippet | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | "); } @@ -701,18 +1029,18 @@ create table t(a text, b int); create function f() returns text as 'select 1::text' language sql; select $0 from t; "), @r" - label | kind | detail | insert_text - --------------------+----------+-------------------------+------------- - a | Column | text | - b | Column | int | - t | Table | | - f() | Function | public.f() returns text | - * | Operator | | - public | Schema | | - pg_catalog | Schema | | - pg_temp | Schema | | - pg_toast | Schema | | - information_schema | Schema | | + label | kind | detail + --------------------+----------+------------------------- + a | Column | text + b | Column | int + t | Table | + f() | Function | public.f() returns text + * | Operator | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | "); } @@ -722,10 +1050,10 @@ select $0 from t; create table t (c int); select t.$0 from t; "), @r" - label | kind | detail | insert_text - -------+----------+--------+------------- - c | Column | int | - * | Operator | | + label | kind | detail + -------+----------+-------- + c | Column | int + * | Operator | "); } @@ -735,15 +1063,15 @@ select t.$0 from t; with t as (select 1 a) select $0 from t; "), @r" - label | kind | detail | insert_text - --------------------+----------+---------+------------- - a | Column | integer | - * | Operator | | - public | Schema | | - pg_catalog | Schema | | - pg_temp | Schema | | - pg_toast | Schema | | - information_schema | Schema | | + label | kind | detail + --------------------+----------+--------- + a | Column | integer + * | Operator | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | "); } @@ -753,17 +1081,17 @@ select $0 from t; with t as (values (1, 'foo', false)) select $0 from t; "), @r" - label | kind | detail | insert_text - --------------------+----------+---------+------------- - column1 | Column | integer | - column2 | Column | text | - column3 | Column | boolean | - * | Operator | | - public | Schema | | - pg_catalog | Schema | | - pg_temp | Schema | | - pg_toast | Schema | | - information_schema | Schema | | + label | kind | detail + --------------------+----------+--------- + column1 | Column | integer + column2 | Column | text + column3 | Column | boolean + * | Operator | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | "); } @@ -772,18 +1100,18 @@ select $0 from t; assert_snapshot!(completions(" select $0 from (values (1, 'foo', 1.5, false)); "), @r" - label | kind | detail | insert_text - --------------------+----------+---------+------------- - column1 | Column | integer | - column2 | Column | text | - column3 | Column | numeric | - column4 | Column | boolean | - * | Operator | | - public | Schema | | - pg_catalog | Schema | | - pg_temp | Schema | | - pg_toast | Schema | | - information_schema | Schema | | + label | kind | detail + --------------------+----------+--------- + column1 | Column | integer + column2 | Column | text + column3 | Column | numeric + column4 | Column | boolean + * | Operator | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | "); } @@ -794,9 +1122,9 @@ create function f() returns int8 as 'select 1' language sql; create function foo.b() returns int8 as 'select 2' language sql; select public.$0; "), @r" - label | kind | detail | insert_text - -------+----------+-------------------------+------------- - f() | Function | public.f() returns int8 | + label | kind | detail + -------+----------+------------------------- + f() | Function | public.f() returns int8 "); } @@ -806,9 +1134,9 @@ select public.$0; create table users (id int); truncate public.$0; "), @r" - label | kind | detail | insert_text - -------+-------+--------+------------- - users | Table | | + label | kind | detail + -------+-------+-------- + users | Table | "); } @@ -818,14 +1146,14 @@ truncate public.$0; create table users (id int); delete from $0; "), @r" - label | kind | detail | insert_text - --------------------+--------+--------+------------- - users | Table | | - public | Schema | | - pg_catalog | Schema | | - pg_temp | Schema | | - pg_toast | Schema | | - information_schema | Schema | | + label | kind | detail + --------------------+--------+-------- + users | Table | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | "); } @@ -835,11 +1163,11 @@ delete from $0; create table t (id int); delete from t $0; "), @r" - label | kind | detail | insert_text - -----------+---------+--------+-------------- - returning | Keyword | | returning $0 - using | Keyword | | using $0 - where | Keyword | | where $0 + label | kind | detail + -----------+---------+-------- + returning | Snippet | + using | Snippet | + where | Snippet | "); } @@ -850,12 +1178,12 @@ create table t (id int, name text); create function is_active() returns bool as 'select true' language sql; delete from t where $0; "), @r" - label | kind | detail | insert_text - -------------+----------+---------------------------------+------------- - id | Column | int | - name | Column | text | - t | Table | | - is_active() | Function | public.is_active() returns bool | + label | kind | detail + -------------+----------+--------------------------------- + id | Column | int + name | Column | text + t | Table | + is_active() | Function | public.is_active() returns bool ") } @@ -865,11 +1193,11 @@ delete from t where $0; create table t (id int, name text); delete from t returning $0; "), @r" - label | kind | detail | insert_text - -------+--------+--------+------------- - id | Column | int | - name | Column | text | - t | Table | | + label | kind | detail + -------+--------+-------- + id | Column | int + name | Column | text + t | Table | "); } @@ -886,11 +1214,108 @@ create function f(t) returns int8 create table t (a int, b text); delete from t where t.$0; "), @r" - label | kind | detail | insert_text - -------+----------+--------+------------- - a | Column | int | - b | Column | text | - f | Function | | + label | kind | detail + -------+----------+-------- + a | Column | int + b | Column | text + f | Function | + "); + } + + #[test] + fn completion_select_clauses() { + assert_snapshot!(completions(" +with t as (select 1 a) +select a from t $0; +"), @r" + label | kind | detail + -----------+---------+-------- + except | Snippet | + fetch | Snippet | + for | Snippet | + group by | Snippet | + having | Snippet | + intersect | Snippet | + limit | Snippet | + offset | Snippet | + order by | Snippet | + union | Snippet | + where | Snippet | + window | Snippet | + "); + } + + #[test] + fn completion_select_clauses_simple() { + assert_snapshot!(completions(" +select 1 from t $0; +"), @r" + label | kind | detail + -----------+---------+-------- + except | Snippet | + fetch | Snippet | + for | Snippet | + group by | Snippet | + having | Snippet | + intersect | Snippet | + limit | Snippet | + offset | Snippet | + order by | Snippet | + union | Snippet | + where | Snippet | + window | Snippet | + "); + } + + #[test] + fn completion_select_group_by_expr() { + assert_snapshot!(completions(" +with t as (select 1 a) +select a from t group by $0; +"), @r" + label | kind | detail + -------+--------+--------- + a | Column | integer + t | Table | + "); + } + + #[test] + fn completion_select_where_expr() { + assert_snapshot!(completions(" +create table t (id int, name text); +select * from t where $0; +"), @r" + label | kind | detail + -------+--------+-------- + id | Column | int + name | Column | text + t | Table | + "); + } + + #[test] + fn completion_select_limit() { + assert_snapshot!(completions(" +create function get_limit() returns int as 'select 10' language sql; +select 1 from t limit $0; +"), @r" + label | kind | detail + -------------+----------+-------------------------------- + all | Keyword | + get_limit() | Function | public.get_limit() returns int + "); + } + + #[test] + fn completion_select_offset() { + assert_snapshot!(completions(" +create function get_offset() returns int as 'select 10' language sql; +select 1 from t offset $0; +"), @r" + label | kind | detail + --------------+----------+--------------------------------- + get_offset() | Function | public.get_offset() returns int "); } }