diff --git a/Cargo.lock b/Cargo.lock index a7a116ab9..b5f2f6f57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -749,7 +749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.13", "serde", ] @@ -1667,7 +1667,7 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", + "regex-automata 0.4.13", "regex-syntax 0.8.5", ] @@ -1971,7 +1971,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.13", "same-file", "walkdir", "winapi-util", @@ -2717,11 +2717,13 @@ dependencies = [ "async-std", "criterion", "fuzzy-matcher", + "insta", "pgls_schema_cache", "pgls_test_utils", "pgls_text_size", "pgls_treesitter", "pgls_treesitter_grammar", + "regex", "schemars", "serde", "serde_json", @@ -2729,6 +2731,7 @@ dependencies = [ "tokio", "tracing", "tree-sitter", + "unindent", ] [[package]] @@ -3648,13 +3651,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.13", "regex-syntax 0.8.5", ] @@ -3669,9 +3672,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -5039,6 +5042,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/bun.lock b/bun.lock index 718b63324..793040af6 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "postgres_lsp", diff --git a/crates/pgls_completions/Cargo.toml b/crates/pgls_completions/Cargo.toml index cfb4fa864..43c0c3f4d 100644 --- a/crates/pgls_completions/Cargo.toml +++ b/crates/pgls_completions/Cargo.toml @@ -32,7 +32,11 @@ tokio = { version = "1.41.1", features = ["full"] } [dev-dependencies] criterion.workspace = true +insta.workspace = true pgls_test_utils.workspace = true +regex = "1.12.2" +unindent = "0.2.4" + [lib] doctest = false diff --git a/crates/pgls_completions/src/builder.rs b/crates/pgls_completions/src/builder.rs index db6be9e1c..1bb0075a7 100644 --- a/crates/pgls_completions/src/builder.rs +++ b/crates/pgls_completions/src/builder.rs @@ -2,6 +2,7 @@ use crate::{ CompletionItemKind, CompletionText, item::CompletionItem, relevance::{filtering::CompletionFilter, scoring::CompletionScore}, + sanitization, }; use pgls_treesitter::TreesitterContext; @@ -24,6 +25,12 @@ pub(crate) struct CompletionBuilder<'a> { impl<'a> CompletionBuilder<'a> { pub fn new(ctx: &'a TreesitterContext) -> Self { + println!( + "is sanitized: {:#?}", + ctx.get_node_under_cursor_content() + .map(|txt| sanitization::is_sanitized_token(txt.as_str())) + ); + CompletionBuilder { items: vec![], ctx } } @@ -42,6 +49,11 @@ impl<'a> CompletionBuilder<'a> { item.score.calc_score(self.ctx); } + items = items + .into_iter() + .filter(|i| !i.score.should_skip()) + .collect(); + items.sort_by(|a, b| { b.score .get_score() diff --git a/crates/pgls_completions/src/providers/columns.rs b/crates/pgls_completions/src/providers/columns.rs index a902579e4..863a6a8ed 100644 --- a/crates/pgls_completions/src/providers/columns.rs +++ b/crates/pgls_completions/src/providers/columns.rs @@ -50,36 +50,16 @@ fn get_completion_text(ctx: &TreesitterContext, col: &Column) -> CompletionText #[cfg(test)] mod tests { - use std::vec; - - use pgls_text_size::TextRange; - use sqlx::{Executor, PgPool}; - - use crate::{ - CompletionItem, CompletionItemKind, complete, - test_helper::{ - CompletionAssertion, assert_complete_results, get_test_deps, get_test_params, - }, - }; - use pgls_test_utils::QueryWithCursorPosition; + use sqlx::PgPool; - struct TestCase { - query: String, - message: &'static str, - label: &'static str, - description: &'static str, - } - - impl TestCase { - fn get_input_query(&self) -> QueryWithCursorPosition { - let strs: Vec<&str> = self.query.split_whitespace().collect(); - strs.join(" ").as_str().into() - } - } + use crate::test_helper::{ + TestCompletionsCase, TestCompletionsSuite, assert_complete_results, + assert_no_complete_results, + }; #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] - async fn completes_columns(pool: PgPool) { + async fn handles_nested_queries(pool: PgPool) { let setup = r#" create schema private; @@ -99,62 +79,20 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - let queries: Vec = vec![ - TestCase { - message: "correctly prefers the columns of present tables", - query: format!( - r#"select na{} from public.audio_books;"#, - QueryWithCursorPosition::cursor_marker() - ), - label: "narrator", - description: "public.audio_books", - }, - TestCase { - message: "correctly handles nested queries", - query: format!( - r#" - select - * - from ( - select id, na{} - from private.audio_books - ) as subquery - join public.users u - on u.id = subquery.id; - "#, - QueryWithCursorPosition::cursor_marker() - ), - label: "narrator_id", - description: "private.audio_books", - }, - TestCase { - message: "works without a schema", - query: format!( - r#"select na{} from users;"#, - QueryWithCursorPosition::cursor_marker() - ), - label: "name", - description: "public.users", - }, - ]; - - for q in queries { - let (tree, cache) = get_test_deps(None, q.get_input_query(), &pool).await; - let params = get_test_params(&tree, &cache, q.get_input_query()); - let results = complete(params); - - let CompletionItem { - label, description, .. - } = results - .into_iter() - .next() - .expect("Should return at least one completion item"); - - assert_eq!(label, q.label, "{}", q.message); - assert_eq!(description, q.description, "{}", q.message); - } + TestCompletionsSuite::new(&pool, Some(setup)).with_case( + TestCompletionsCase::new() + .inside_static_statement(r#" + select * from ( + + ) as subquery + join public.users u + on u.id = subquery.id; + "#) + .type_sql("select id, narrator_id<1> from private.audio_books") + .comment("Should prefer the one from private.audio_audiobooks, since the other tables are out of scope.") + ) + .snapshot("handles_nested_queries") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -178,49 +116,14 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - let case = TestCase { - query: format!(r#"select n{};"#, QueryWithCursorPosition::cursor_marker()), - description: "", - label: "", - message: "", - }; - - let (tree, cache) = get_test_deps(None, case.get_input_query(), &pool).await; - let params = get_test_params(&tree, &cache, case.get_input_query()); - let mut items = complete(params); - - let _ = items.split_off(4); - - #[derive(Eq, PartialEq, Debug)] - struct LabelAndDesc { - label: String, - desc: String, - } - - let labels: Vec = items - .into_iter() - .map(|c| LabelAndDesc { - label: c.label, - desc: c.description, - }) - .collect(); - - let expected = vec![ - ("name", "public.users"), - ("narrator", "public.audio_books"), - ("narrator_id", "private.audio_books"), - ("id", "public.audio_books"), - ] - .into_iter() - .map(|(label, schema)| LabelAndDesc { - label: label.into(), - desc: schema.into(), - }) - .collect::>(); - - assert_eq!(labels, expected); + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .type_sql("select narrator_id<1>") + .comment("Should suggest all columns with n first"), + ) + .snapshot("shows_multiple_columns_if_no_relation_specified") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -234,44 +137,10 @@ mod tests { ); "#; - let test_case = TestCase { - message: "suggests user created tables first", - query: format!( - r#"select {} from users"#, - QueryWithCursorPosition::cursor_marker() - ), - label: "", - description: "", - }; - - let (tree, cache) = get_test_deps(Some(setup), test_case.get_input_query(), &pool).await; - let params = get_test_params(&tree, &cache, test_case.get_input_query()); - let results = complete(params); - - let (first_four, _rest) = results.split_at(4); - - let has_column_in_first_four = |col: &'static str| { - first_four - .iter() - .any(|compl_item| compl_item.label.as_str() == col) - }; - - assert!( - has_column_in_first_four("id"), - "`id` not present in first four completion items." - ); - assert!( - has_column_in_first_four("name"), - "`name` not present in first four completion items." - ); - assert!( - has_column_in_first_four("address"), - "`address` not present in first four completion items." - ); - assert!( - has_column_in_first_four("email"), - "`email` not present in first four completion items." - ); + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case(TestCompletionsCase::new().type_sql("select name from users")) + .snapshot("suggests_relevant_columns_without_letters") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -283,29 +152,20 @@ mod tests { id serial primary key, name text, address text, - email text + email text, + public boolean ); "#; - let test_case = TestCase { - message: "suggests user created tables first", - query: format!( - r#"select * from private.{}"#, - QueryWithCursorPosition::cursor_marker() - ), - label: "", - description: "", - }; - - let (tree, cache) = get_test_deps(Some(setup), test_case.get_input_query(), &pool).await; - let params = get_test_params(&tree, &cache, test_case.get_input_query()); - let results = complete(params); - - assert!( - !results - .into_iter() - .any(|item| item.kind == CompletionItemKind::Column) - ); + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .inside_static_statement("select * from ") + .type_sql("private<1>.users") + .comment("No column suggestions."), + ) + .snapshot("ignores_cols_in_from_clause") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -330,58 +190,65 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .inside_static_statement(" from public.users") + .type_sql("select address2<1>") + .comment("Should suggest address 2 from public table"), + ) + .with_case( + TestCompletionsCase::new() + .inside_static_statement(" from private.users") + .type_sql("select address1<1>") + .comment("Should suggest address 1 from private table"), + ) + .with_case( + TestCompletionsCase::new() + .inside_static_statement(" from private.users") + .type_sql("select settings<1>") + .comment("Should prioritize columns starting with s"), + ) + .snapshot("prefers_columns_of_mentioned_tables") + .await; + } - assert_complete_results( - format!( - r#"select {} from users"#, - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("address2".into()), - CompletionAssertion::Label("email2".into()), - CompletionAssertion::Label("id2".into()), - CompletionAssertion::Label("name2".into()), - ], - None, - &pool, - ) - .await; + #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] + async fn filters_out_by_aliases_in_join_on(pool: PgPool) { + let setup = r#" + create schema auth; - assert_complete_results( - format!( - r#"select {} from private.users"#, - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("address1".into()), - CompletionAssertion::Label("email1".into()), - CompletionAssertion::Label("id1".into()), - CompletionAssertion::Label("name1".into()), - ], - None, - &pool, - ) - .await; + create table auth.users ( + uid serial primary key, + name text not null, + email text unique not null + ); - // asserts fuzzy finding for "settings" - assert_complete_results( - format!( - r#"select sett{} from private.users"#, - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![CompletionAssertion::Label("user_settings".into())], - None, - &pool, - ) - .await; + create table auth.posts ( + pid serial primary key, + user_id int not null references auth.users(uid), + title text not null, + content text, + created_at timestamp default now() + ); + "#; + + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .inside_static_statement( + "select u.id, p.content from auth.users u join auth.posts p ", + ) + .type_sql("on u<1>.id = p.<2>user_id") + .comment("Should prefer primary indices here.") + .comment("We should only get columns from the auth.posts table."), + ) + .snapshot("filters_out_by_aliases_in_join_on") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] - async fn filters_out_by_aliases(pool: PgPool) { + async fn filters_out_by_aliases_in_select(pool: PgPool) { let setup = r#" create schema auth; @@ -400,52 +267,17 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - // test in SELECT clause - assert_complete_results( - format!( - "select u.id, p.{} from auth.users u join auth.posts p on u.id = p.user_id;", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::LabelNotExists("uid".to_string()), - CompletionAssertion::LabelNotExists("name".to_string()), - CompletionAssertion::LabelNotExists("email".to_string()), - CompletionAssertion::Label("content".to_string()), - CompletionAssertion::Label("created_at".to_string()), - CompletionAssertion::Label("pid".to_string()), - CompletionAssertion::Label("title".to_string()), - CompletionAssertion::Label("user_id".to_string()), - ], - None, - &pool, - ) - .await; - - // test in JOIN clause - assert_complete_results( - format!( - "select u.id, p.content from auth.users u join auth.posts p on u.id = p.{};", - QueryWithCursorPosition::cursor_marker() + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .inside_static_statement( + " from auth.users u join auth.posts p on u.id = p.user_id;", + ) + .type_sql("select u.id, p.pid<1>") + .comment("We should only get columns from the auth.posts table."), ) - .as_str(), - vec![ - CompletionAssertion::LabelNotExists("uid".to_string()), - CompletionAssertion::LabelNotExists("name".to_string()), - CompletionAssertion::LabelNotExists("email".to_string()), - // primary keys are preferred - CompletionAssertion::Label("pid".to_string()), - CompletionAssertion::Label("content".to_string()), - CompletionAssertion::Label("created_at".to_string()), - CompletionAssertion::Label("title".to_string()), - CompletionAssertion::Label("user_id".to_string()), - ], - None, - &pool, - ) - .await; + .snapshot("filters_out_by_aliases_in_select") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -468,24 +300,12 @@ mod tests { ); "#; - /* - * We are not in the "ON" part of the JOIN clause, so we should not complete columns. - */ - assert_complete_results( - format!( - "select u.id, p.content from auth.users u join auth.{}", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::KindNotExists(CompletionItemKind::Column), - CompletionAssertion::LabelAndKind("posts".to_string(), CompletionItemKind::Table), - CompletionAssertion::LabelAndKind("users".to_string(), CompletionItemKind::Table), - ], - Some(setup), - &pool, - ) - .await; + TestCompletionsSuite::new(&pool, Some(setup)).with_case( + TestCompletionsCase::new() + .type_sql("select u.uid, p.content from auth<1>.users<2> u join auth.posts p on u.uid = p.user_id") + .comment("Schema suggestions should be prioritized, since we want to push users to specify them.") + .comment("Here, we shouldn't have schema completions.") + ).snapshot("does_not_complete_cols_in_join_clauses").await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -508,41 +328,16 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - assert_complete_results( - format!( - "select u.id, auth.posts.content from auth.users u join auth.posts on u.{}", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::KindNotExists(CompletionItemKind::Table), - CompletionAssertion::LabelAndKind("uid".to_string(), CompletionItemKind::Column), - CompletionAssertion::LabelAndKind("email".to_string(), CompletionItemKind::Column), - CompletionAssertion::LabelAndKind("name".to_string(), CompletionItemKind::Column), - ], - None, - &pool, - ) - .await; - - assert_complete_results( - format!( - "select u.id, p.content from auth.users u join auth.posts p on p.user_id = u.{}", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::KindNotExists(CompletionItemKind::Table), - CompletionAssertion::LabelAndKind("uid".to_string(), CompletionItemKind::Column), - CompletionAssertion::LabelAndKind("email".to_string(), CompletionItemKind::Column), - CompletionAssertion::LabelAndKind("name".to_string(), CompletionItemKind::Column), - ], - None, - &pool, - ) - .await; + TestCompletionsSuite::new(&pool, Some(setup)).with_case( + TestCompletionsCase::new() + .inside_static_statement( + "select u.id, auth.posts.content from auth.users u join auth.posts p on ", + ) + .type_sql("<1>p.user_id<2> = u.uid<3>;") + .comment("Should prioritize primary keys here.") + .comment("Should only consider columns from auth.posts here.") + .comment("Should only consider columns from auth.users here.") + ).snapshot("completes_in_join_on_clause").await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -565,76 +360,26 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - assert_complete_results( - format!( - "select {} from public.one o join public.two on o.id = t.id;", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("a".to_string()), - CompletionAssertion::Label("b".to_string()), - CompletionAssertion::Label("c".to_string()), - CompletionAssertion::Label("d".to_string()), - CompletionAssertion::Label("e".to_string()), - ], - None, - &pool, - ) - .await; - - // "a" is already mentioned, so it jumps down - assert_complete_results( - format!( - "select a, {} from public.one o join public.two on o.id = t.id;", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("b".to_string()), - CompletionAssertion::Label("c".to_string()), - CompletionAssertion::Label("d".to_string()), - CompletionAssertion::Label("e".to_string()), - CompletionAssertion::Label("id".to_string()), - CompletionAssertion::Label("z".to_string()), - CompletionAssertion::Label("a".to_string()), - ], - None, - &pool, - ) - .await; - - // "id" of table one is mentioned, but table two isn't – - // its priority stays up - assert_complete_results( - format!( - "select o.id, a, b, c, d, e, {} from public.one o join public.two on o.id = t.id;", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::LabelAndDesc("id".to_string(), "public.two".to_string()), - CompletionAssertion::Label("z".to_string()), - ], - None, - &pool, - ) - .await; - - // "id" is ambiguous, so both "id" columns are lowered in priority - assert_complete_results( - format!( - "select id, a, b, c, d, e, {} from public.one o join public.two on o.id = t.id;", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![CompletionAssertion::Label("z".to_string())], - None, - &pool, - ) - .await; + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .inside_static_statement( + " from public.one o join public.two on o.id = t.id;", + ) + .type_sql("select o.id, a, <1>b, c, d, e, <2>z") + .comment("Should have low priority for `a`, since it's already mentioned.") + .comment("Should have high priority of id of table two, but not one, since it's already mentioned.") + ) + .with_case( + TestCompletionsCase::new() + .inside_static_statement( + " from public.one o join public.two on o.id = t.id;", + ) + .type_sql("select id, a, b, c, d, e, <1>z") + .comment("`id` could be from both tables, so both priorities are lowered."), + ) + .snapshot("prefers_not_mentioned_columns") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -653,69 +398,25 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - // We should prefer the instrument columns, even though they - // are lower in the alphabet - - assert_complete_results( - format!( - "insert into instruments ({})", - QueryWithCursorPosition::cursor_marker() + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .type_sql("insert into instruments (id, name) values (1, 'my_bass');"), ) - .as_str(), - vec![ - CompletionAssertion::Label("id".to_string()), - CompletionAssertion::Label("name".to_string()), - CompletionAssertion::Label("z".to_string()), - ], - None, - &pool, - ) - .await; - - assert_complete_results( - format!( - "insert into instruments (id, {})", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("name".to_string()), - CompletionAssertion::Label("z".to_string()), - ], - None, - &pool, - ) - .await; - - assert_complete_results( - format!( - "insert into instruments (id, {}, name)", - QueryWithCursorPosition::cursor_marker() + .with_case( + TestCompletionsCase::new() + .type_sql(r#"insert into instruments ("id", "name") values (1, 'my_bass');"#), ) - .as_str(), - vec![CompletionAssertion::Label("z".to_string())], - None, - &pool, - ) - .await; - - // works with completed statement - assert_complete_results( - format!( - "insert into instruments (name, {}) values ('my_bass');", - QueryWithCursorPosition::cursor_marker() + .with_case( + TestCompletionsCase::new() + .inside_static_statement( + r#"insert into instruments (, name) values ('my_bass');"#, + ) + .type_sql("id, <1>z") + .comment("`name` is already written, so z should be suggested."), ) - .as_str(), - vec![ - CompletionAssertion::Label("id".to_string()), - CompletionAssertion::Label("z".to_string()), - ], - None, - &pool, - ) - .await; + .snapshot("suggests_columns_in_insert_clause") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -735,74 +436,19 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - assert_complete_results( - format!( - "select name from instruments where {} ", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("created_at".into()), - CompletionAssertion::Label("id".into()), - CompletionAssertion::Label("name".into()), - CompletionAssertion::Label("z".into()), - ], - None, - &pool, - ) - .await; - - assert_complete_results( - format!( - "select name from instruments where z = 'something' and created_at > {}", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - // simply do not complete columns + schemas; functions etc. are ok - vec![ - CompletionAssertion::KindNotExists(CompletionItemKind::Column), - CompletionAssertion::KindNotExists(CompletionItemKind::Schema), - ], - None, - &pool, - ) - .await; - - // prefers not mentioned columns - assert_complete_results( - format!( - "select name from instruments where id = 'something' and {}", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("created_at".into()), - CompletionAssertion::Label("name".into()), - CompletionAssertion::Label("z".into()), - ], - None, - &pool, - ) - .await; - - // // uses aliases - assert_complete_results( - format!( - "select name from instruments i join others o on i.z = o.a where i.{}", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("created_at".into()), - CompletionAssertion::Label("id".into()), - CompletionAssertion::Label("name".into()), - ], - None, - &pool, - ) - .await; + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .inside_static_statement( + "select name from instruments i join others o on i.z = o.a ", + ) + .type_sql("where o.<1>a = <2>i.z and <3>i.id > 5;") + .comment("should respect alias speciifcation") + .comment("should not prioritize suggest columns or schemas (right side of binary expression)") + .comment("should prioritize columns that aren't already mentioned") + ) + .snapshot("suggests_columns_in_where_clause") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -814,65 +460,42 @@ mod tests { z text, created_at timestamp with time zone default now() ); - - create table others ( - a text, - b text, - c text - ); "#; - pool.execute(setup).await.unwrap(); - - let queries = vec![ - format!( - "alter table instruments drop column {}", - QueryWithCursorPosition::cursor_marker() - ), - format!( - "alter table instruments drop column if exists {}", - QueryWithCursorPosition::cursor_marker() - ), - format!( - "alter table instruments alter column {} set default", - QueryWithCursorPosition::cursor_marker() - ), - format!( - "alter table instruments alter {} set default", - QueryWithCursorPosition::cursor_marker() - ), - format!( - "alter table public.instruments alter column {}", - QueryWithCursorPosition::cursor_marker() - ), - format!( - "alter table instruments alter {}", - QueryWithCursorPosition::cursor_marker() - ), - format!( - "alter table instruments rename {} to new_col", - QueryWithCursorPosition::cursor_marker() - ), - format!( - "alter table public.instruments rename column {} to new_col", - QueryWithCursorPosition::cursor_marker() - ), - ]; - - for query in queries { - assert_complete_results( - query.as_str(), - vec![ - CompletionAssertion::Label("created_at".into()), - CompletionAssertion::Label("id".into()), - CompletionAssertion::Label("name".into()), - CompletionAssertion::Label("z".into()), - ], - None, - &pool, + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new().type_sql("alter table instruments drop column name"), + ) + .with_case( + TestCompletionsCase::new().type_sql("alter table instruments drop column name"), + ) + .with_case( + TestCompletionsCase::new() + .type_sql("alter table instruments drop column if exists name"), + ) + .with_case( + TestCompletionsCase::new() + .type_sql("alter table instruments alter column name set default"), + ) + .with_case( + TestCompletionsCase::new() + .type_sql("alter table instruments alter name set default"), + ) + .with_case( + TestCompletionsCase::new() + .type_sql("alter table public.instruments alter column name"), ) + .with_case(TestCompletionsCase::new().type_sql("alter table instruments alter name")) + .with_case( + TestCompletionsCase::new() + .type_sql("alter table instruments rename name to new_col"), + ) + .with_case( + TestCompletionsCase::new() + .type_sql("alter table public.instruments rename column name to new_col"), + ) + .snapshot("suggests_columns_in_alter_table_and_drop_table") .await; - } } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -886,41 +509,23 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - let col_queries = vec![ - format!( - r#"create policy "my_pol" on public.instruments for select using ({})"#, - QueryWithCursorPosition::cursor_marker() - ), - format!( - r#"create policy "my_pol" on public.instruments for insert with check ({})"#, - QueryWithCursorPosition::cursor_marker() - ), - format!( - r#"create policy "my_pol" on public.instruments for update using (id = 1 and {})"#, - QueryWithCursorPosition::cursor_marker() - ), - format!( - r#"create policy "my_pol" on public.instruments for insert with check (id = 1 and {})"#, - QueryWithCursorPosition::cursor_marker() - ), - ]; - - for query in col_queries { - assert_complete_results( - query.as_str(), - vec![ - CompletionAssertion::Label("created_at".into()), - CompletionAssertion::Label("id".into()), - CompletionAssertion::Label("name".into()), - CompletionAssertion::Label("z".into()), - ], - None, - &pool, - ) + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .inside_static_statement( + r#"create policy "my_pol" on public.instruments for select using ()"#, + ) + .type_sql("id = 1 and created_at > '2025-01-01'"), + ) + .with_case( + TestCompletionsCase::new() + .inside_static_statement( + r#"create policy "my_pol" on public.instruments for insert with check ()"#, + ) + .type_sql("id = 1 and created_at > '2025-01-01'"), + ) + .snapshot("suggests_columns_policy_using_clause") .await; - } } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -936,75 +541,23 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - // test completion inside quoted column name - assert_complete_results( - format!( - r#"select "em{}" from "private"."users""#, - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![CompletionAssertion::LabelAndDesc( - "email".to_string(), - "private.users".to_string(), - )], - None, - &pool, - ) - .await; - - // test completion for already quoted column - assert_complete_results( - format!( - r#"select "quoted_col{}" from "private"."users""#, - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![CompletionAssertion::LabelAndDesc( - "quoted_column".to_string(), - "private.users".to_string(), - )], - None, - &pool, - ) - .await; - - // test completion with empty quotes - assert_complete_results( - format!( - r#"select "{}" from "private"."users""#, - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("email".to_string()), - CompletionAssertion::Label("id".to_string()), - CompletionAssertion::Label("name".to_string()), - CompletionAssertion::Label("quoted_column".to_string()), - ], - None, - &pool, - ) - .await; - - // test completion with partially opened quote - assert_complete_results( - format!( - r#"select "{} from "private"."users""#, - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::Label("email".to_string()), - CompletionAssertion::Label("id".to_string()), - CompletionAssertion::Label("name".to_string()), - CompletionAssertion::Label("quoted_column".to_string()), - ], - None, - &pool, - ) - .await; + // assert_no_complete_results( + // format!( + // r#"select "email" from "p{}""#, + // QueryWithCursorPosition::cursor_marker() + // ) + // .as_str(), + // Some(setup), + // &pool, + // ) + // .await; + + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new().type_sql(r#"select "email" from "private"."users";"#), + ) + .snapshot("completes_quoted_columns") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -1025,240 +578,40 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - { - // should suggest pr"."email and insert into existing quotes - let query = format!( - r#"select "e{}" from private.users "pr""#, - QueryWithCursorPosition::cursor_marker() - ); - - assert_complete_results( - query.as_str(), - vec![CompletionAssertion::CompletionTextAndRange( - r#"pr"."email"#.into(), - // replaces the full `"e"` - TextRange::new(8.into(), 9.into()), - )], - None, - &pool, - ) - .await; - } - - { - // should suggest pr"."email and insert into existing quotes - let query = format!( - r#"select "{}" from private.users "pr""#, - QueryWithCursorPosition::cursor_marker() - ); - - assert_complete_results( - query.as_str(), - vec![CompletionAssertion::CompletionTextAndRange( - r#"pr"."email"#.into(), - TextRange::new(8.into(), 8.into()), - )], - None, - &pool, - ) - .await; - } - - { - // should suggest email and insert into quotes - let query = format!( - r#"select pr."{}" from private.users "pr""#, - QueryWithCursorPosition::cursor_marker() - ); - assert_complete_results( - query.as_str(), - vec![CompletionAssertion::CompletionTextAndRange( - r#"email"#.into(), - TextRange::new(11.into(), 11.into()), - )], - None, - &pool, - ) - .await; - } - - { - // should suggest email - let query = format!( - r#"select "pr".{} from private.users "pr""#, - QueryWithCursorPosition::cursor_marker() - ); - assert_complete_results( - query.as_str(), - vec![CompletionAssertion::CompletionTextAndRange( - "email".into(), - TextRange::new(12.into(), 12.into()), - )], - None, - &pool, - ) - .await; - } - - { - // should suggest `email` - let query = format!( - r#"select pr.{} from private.users "pr""#, - QueryWithCursorPosition::cursor_marker() - ); - assert_complete_results( - query.as_str(), - vec![CompletionAssertion::CompletionTextAndRange( - "email".into(), - TextRange::new(10.into(), 10.into()), - )], - None, - &pool, - ) - .await; - } - - { - let query = format!( - r#"select {} from private.users "pr" join public.names n on pr.id = n.uid;"#, - QueryWithCursorPosition::cursor_marker() - ); - assert_complete_results( - query.as_str(), - vec![ - CompletionAssertion::CompletionTextAndRange( - "n.name".into(), - TextRange::new(7.into(), 7.into()), - ), - CompletionAssertion::CompletionTextAndRange( - "n.uid".into(), - TextRange::new(7.into(), 7.into()), - ), - CompletionAssertion::CompletionTextAndRange( - r#""pr".email"#.into(), - TextRange::new(7.into(), 7.into()), - ), - CompletionAssertion::CompletionTextAndRange( - r#""pr".id"#.into(), - TextRange::new(7.into(), 7.into()), - ), - ], - None, - &pool, - ) - .await; - } - - { - // should suggest "pr"."email" - let query = format!( - r#"select "{}" from private.users "pr" join public.names "n" on pr.id = n.uid;"#, - QueryWithCursorPosition::cursor_marker() - ); - assert_complete_results( - query.as_str(), - vec![ - CompletionAssertion::CompletionTextAndRange( - r#"n"."name"#.into(), - TextRange::new(8.into(), 8.into()), - ), - CompletionAssertion::CompletionTextAndRange( - r#"n"."uid"#.into(), - TextRange::new(8.into(), 8.into()), - ), - CompletionAssertion::CompletionTextAndRange( - r#"pr"."email"#.into(), - TextRange::new(8.into(), 8.into()), - ), - CompletionAssertion::CompletionTextAndRange( - r#"pr"."id"#.into(), - TextRange::new(8.into(), 8.into()), - ), - ], - None, - &pool, - ) - .await; - } - - { - // should suggest pr"."email" - let query = format!( - r#"select "{} from private.users "pr";"#, - QueryWithCursorPosition::cursor_marker() - ); - assert_complete_results( - query.as_str(), - vec![ - CompletionAssertion::CompletionTextAndRange( - r#"pr"."email""#.into(), - TextRange::new(8.into(), 8.into()), - ), - CompletionAssertion::CompletionTextAndRange( - r#"pr"."id""#.into(), - TextRange::new(8.into(), 8.into()), - ), - ], - None, - &pool, - ) - .await; - } - - { - // should suggest email" - let query = format!( - r#"select pr."{} from private.users "pr";"#, - QueryWithCursorPosition::cursor_marker() - ); - assert_complete_results( - query.as_str(), - vec![CompletionAssertion::CompletionTextAndRange( - r#"email""#.into(), - TextRange::new(11.into(), 11.into()), - )], - None, - &pool, - ) - .await; - } - - { - // should suggest email" - let query = format!( - r#"select "pr"."{} from private.users "pr";"#, - QueryWithCursorPosition::cursor_marker() - ); - assert_complete_results( - query.as_str(), - vec![CompletionAssertion::CompletionTextAndRange( - r#"email""#.into(), - TextRange::new(13.into(), 13.into()), - )], - None, - &pool, - ) - .await; - } - - { - // should suggest "n".name - let query = format!( - r#"select {} from names "n";"#, - QueryWithCursorPosition::cursor_marker() - ); - assert_complete_results( - query.as_str(), - vec![CompletionAssertion::CompletionTextAndRange( - r#""n".name"#.into(), - TextRange::new(7.into(), 7.into()), - )], - None, - &pool, - ) + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new() + .type_sql(r#"select "pr"."email" from private.users "pr""#), + ) + .with_case( + TestCompletionsCase::new() + .inside_static_statement(r#" from private.users "pr""#) + .type_sql(r#"select "email""#), + ) + .with_case( + TestCompletionsCase::new() + .inside_static_statement(r#" from private.users "pr""#) + .type_sql(r#"select pr."email""#), + ) + .with_case( + TestCompletionsCase::new() + .inside_static_statement(r#" from private.users "pr""#) + .type_sql(r#"select "pr"."email""#), + ) + .with_case( + TestCompletionsCase::new() + .inside_static_statement(r#" from private.users "pr""#) + .type_sql(r#"select pr.<1>email"#) + .comment("not quoted here, since the alias isn't."), + ) + .with_case( + TestCompletionsCase::new() + .inside_static_statement( + r#" from private.users "pr" join public.names n on pr.id = n.uid;"#, + ) + .type_sql(r#"select "pr"."email", n.uid"#), + ) + .snapshot("completes_quoted_columns_with_aliases") .await; - } } } diff --git a/crates/pgls_completions/src/relevance/filtering.rs b/crates/pgls_completions/src/relevance/filtering.rs index 7aa3531a6..4207ad3a3 100644 --- a/crates/pgls_completions/src/relevance/filtering.rs +++ b/crates/pgls_completions/src/relevance/filtering.rs @@ -1,6 +1,8 @@ use pgls_schema_cache::ProcKind; use pgls_treesitter::context::{TreesitterContext, WrappingClause, WrappingNode}; +use crate::is_sanitized_token; + use super::CompletionRelevanceData; #[derive(Debug)] @@ -43,6 +45,14 @@ impl CompletionFilter<'_> { return None; } + if ctx + .get_node_under_cursor_content() + .is_some_and(|c| is_sanitized_token(c.as_str())) + && ctx.before_cursor_matches_kind(&["ERROR"]) + { + return None; + } + // "literal" nodes can be identfiers wrapped in quotes: // `select "email" from auth.users;` // Here, "email" is a literal node. diff --git a/crates/pgls_completions/src/relevance/scoring.rs b/crates/pgls_completions/src/relevance/scoring.rs index 68f157926..e7cd2b097 100644 --- a/crates/pgls_completions/src/relevance/scoring.rs +++ b/crates/pgls_completions/src/relevance/scoring.rs @@ -9,6 +9,7 @@ use super::CompletionRelevanceData; #[derive(Debug)] pub(crate) struct CompletionScore<'a> { score: i32, + skip: bool, data: CompletionRelevanceData<'a>, } @@ -16,32 +17,76 @@ impl<'a> From> for CompletionScore<'a> { fn from(value: CompletionRelevanceData<'a>) -> Self { Self { score: 0, + skip: false, data: value, } } } impl CompletionScore<'_> { + pub fn should_skip(&self) -> bool { + self.skip + } + pub fn get_score(&self) -> i32 { self.score } pub fn calc_score(&mut self, ctx: &TreesitterContext) { + let case = (matches!(self.data, CompletionRelevanceData::Schema(_)) + && self.get_schema_name().is_some_and(|s| s == "pg_catalog")) + || (matches!(self.data, CompletionRelevanceData::Table(_)) + && self.get_table_name().is_some_and(|s| s == "parameters")); + + if case { + println!("checking {}", self.get_fully_qualified_name()) + } + self.check_is_user_defined(); + if case { + println!("{} after user-defined check", self.score); + } + self.check_matches_schema(ctx); + if case { + println!("{} after schema match check", self.score); + } self.check_matches_query_input(ctx); + if case { + println!("{} after query input match check", self.score); + } self.check_is_invocation(ctx); + if case { + println!("{} after invocation check", self.score); + } self.check_matching_clause_type(ctx); - self.check_matching_wrapping_node(ctx); + if case { + println!("{} after clause type check", self.score); + } + self.check_without_content(ctx); + if case { + println!("{} after wrapping node check", self.score); + } self.check_relations_in_stmt(ctx); + if case { + println!("{} after relations in stmt check", self.score); + } self.check_columns_in_stmt(ctx); + if case { + println!("{} after columns in stmt check", self.score); + } self.check_is_not_wellknown_migration(ctx); + if case { + println!("{} after well-known migration check", self.score); + } } fn check_matches_query_input(&mut self, ctx: &TreesitterContext) { let content = match ctx.get_node_under_cursor_content() { Some(c) if !sanitization::is_sanitized_token(c.as_str()) => c.replace('"', ""), - _ => return, + _ => { + return; + } }; let name = match self.data { @@ -55,20 +100,85 @@ impl CompletionScore<'_> { let fz_matcher = SkimMatcherV2::default(); - if let Some(score) = - fz_matcher.fuzzy_match(name.as_str(), content.to_ascii_lowercase().as_str()) - { - let scorei32: i32 = score - .try_into() - .expect("The length of the input exceeds i32 capacity"); - - // the scoring value isn't linear. - // here are a couple of samples: - // - item: bytea_string_agg_transfn, input: n, score: 15 - // - item: numeric_uplus, input: n, score: 31 - // - item: settings, input: sett, score: 91 - // - item: user_settings, input: sett, score: 82 - self.score += scorei32 / 2; + let check_against = match &ctx.identifier_qualifiers { + // If both qualifiers are already written out, we must check the item's name itself. + (Some(_), Some(_)) => name.clone(), + + // If only one qualifier is written out, we might look at a schema, a table, or an alias. + (None, Some(qualifier)) => { + if self.get_schema_name().is_some_and(|s| s == qualifier) { + self.get_table_name() + .map(|t| format!("{}.{}", t, name)) + .unwrap_or(name.clone()) + } else if self.get_table_name().is_some_and(|t| t == qualifier) { + name.clone() + } else if ctx + .get_mentioned_table_for_alias(&qualifier) + .is_some_and(|alias_tbl| { + self.get_table_name() + .is_some_and(|item_tbl| alias_tbl == item_tbl) + }) + { + name.clone() + } else { + // the qualifier does not match schema, table, or alias. + // what the hell is it? + // probably a typo. + self.skip = true; + String::new() + } + } + + _ => match self.data { + // for columns and functions, we fuzzy match with a possible alias. + CompletionRelevanceData::Column(_) | CompletionRelevanceData::Policy(_) => self + .get_table_name() + .and_then(|tbl| ctx.get_used_alias_for_table(tbl)) + .map(|t| format!("{}.{}", t, name)) + .unwrap_or(name.clone()), + + // everything else is just fuzzy matched against its name. + _ => name.clone(), + }, + }; + + match fz_matcher.fuzzy_match( + check_against.to_ascii_lowercase().as_str(), + content.to_ascii_lowercase().as_str(), + ) { + Some(score) => { + let scorei32: i32 = score + .try_into() + .expect("The length of the input exceeds i32 capacity"); + + // the scoring value isn't linear. + // here are a couple of samples: + // - item: bytea_string_agg_transfn, input: n, score: 15 + // - item: numeric_uplus, input: n, score: 31 + // - item: settings, input: sett, score: 91 + // - item: user_settings, input: sett, score: 82 + self.score += if check_against == name { + scorei32 / 2 + } else { + scorei32 / 3 + }; + + if matches!(self.data, CompletionRelevanceData::Schema(_)) + && self.get_schema_name().is_some_and(|s| s == "pg_catalog") + { + println!("Debug: Schema pg_catalog match score {}", self.score); + } + + if matches!(self.data, CompletionRelevanceData::Table(_)) + && self.get_table_name().is_some_and(|s| s == "parameters") + { + println!( + "Debug: Table information_schema.parameters match score {}", + self.score + ); + } + } + None => self.skip = true, } } @@ -137,41 +247,49 @@ impl CompletionScore<'_> { } } - fn check_matching_wrapping_node(&mut self, ctx: &TreesitterContext) { - let wrapping_node = match ctx.wrapping_node_kind.as_ref() { - None => return, - Some(wn) => wn, - }; + // ok i think we need a rule set first. + // generally, we want to prefer columns that match a mentioned relation. that's already handled elsewhere. same with schema matches. - let has_qualifier = ctx.has_any_qualifier(); - let has_node_text = ctx + // so, what here? we want to handle the *no content* case. + // in that case, we want to check the current node_kind. + + fn check_without_content(&mut self, ctx: &TreesitterContext) { + // the function is only concerned with cases where the user hasn't typed anything yet. + if ctx .get_node_under_cursor_content() - .is_some_and(|txt| !sanitization::is_sanitized_token(txt.as_str())); + .is_some_and(|c| !sanitization::is_sanitized_token(c.as_str())) + { + return; + } - self.score += match self.data { - CompletionRelevanceData::Table(_) => match wrapping_node { - WrappingNode::Relation if has_qualifier => 15, - WrappingNode::Relation if !has_qualifier => 10, - WrappingNode::BinaryExpression => 5, - _ => -50, - }, - CompletionRelevanceData::Function(_) => match wrapping_node { - WrappingNode::BinaryExpression => 15, - WrappingNode::Relation => 10, - _ => -50, - }, - CompletionRelevanceData::Column(_) => match wrapping_node { - WrappingNode::BinaryExpression => 15, - WrappingNode::Assignment => 15, - _ => -15, - }, - CompletionRelevanceData::Schema(_) => match wrapping_node { - WrappingNode::Relation if !has_qualifier && !has_node_text => 15, - WrappingNode::Relation if !has_qualifier && has_node_text => 0, - _ => -50, + match ctx.node_under_cursor.kind() { + "function_identifier" | "table_identifier" + if self.get_schema_name().is_some_and(|s| s == "public") => + { + self.score += 10; + } + + "schema_identifier" if self.get_schema_name().is_some_and(|s| s != "public") => { + self.score += 10; + } + + "any_identifier" => match self.data { + CompletionRelevanceData::Table(table) => { + if table.schema == "public" { + self.score += 20; + } + } + CompletionRelevanceData::Schema(schema) => { + if schema.name != "public" { + self.score += 10; + } else { + self.score -= 20; + } + } + _ => {} }, - CompletionRelevanceData::Policy(_) => 0, - CompletionRelevanceData::Role(_) => 0, + + _ => return, } } @@ -237,6 +355,21 @@ impl CompletionScore<'_> { } } + fn get_fully_qualified_name(&self) -> String { + match self.data { + CompletionRelevanceData::Schema(s) => s.name.clone(), + CompletionRelevanceData::Column(c) => { + format!("{}.{}.{}", c.schema_name, c.table_name, c.name) + } + CompletionRelevanceData::Table(t) => format!("{}.{}", t.schema, t.name), + CompletionRelevanceData::Function(f) => format!("{}.{}", f.schema, f.name), + CompletionRelevanceData::Policy(p) => { + format!("{}.{}.{}", p.schema_name, p.table_name, p.name) + } + CompletionRelevanceData::Role(r) => r.name.clone(), + } + } + fn get_table_name(&self) -> Option<&str> { match self.data { CompletionRelevanceData::Column(c) => Some(c.table_name.as_str()), diff --git a/crates/pgls_completions/src/sanitization.rs b/crates/pgls_completions/src/sanitization.rs index 045eaae65..2b10ee01d 100644 --- a/crates/pgls_completions/src/sanitization.rs +++ b/crates/pgls_completions/src/sanitization.rs @@ -26,7 +26,8 @@ pub(crate) fn remove_sanitized_token(it: &str) -> String { } pub(crate) fn is_sanitized_token(node_under_cursor_txt: &str) -> bool { - node_under_cursor_txt == SANITIZED_TOKEN || is_sanitized_token_with_quote(node_under_cursor_txt) + node_under_cursor_txt.replace('"', "") == SANITIZED_TOKEN + || is_sanitized_token_with_quote(node_under_cursor_txt) } pub(crate) fn is_sanitized_token_with_quote(node_under_cursor_txt: &str) -> bool { diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_in_join_on_clause.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_in_join_on_clause.snap new file mode 100644 index 000000000..73e340190 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_in_join_on_clause.snap @@ -0,0 +1,88 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema auth; + +create table auth.users ( + uid serial primary key, + name text not null, + email text unique not null +); + +create table auth.posts ( + pid serial primary key, + user_id int not null references auth.users(uid), + title text not null, + content text, + created_at timestamp default now() +); + + +-------------- + +select u.id, auth.posts.content from auth.users u join auth.posts p on | +**Should prioritize primary keys here.** + +Results: +p.pid - auth.posts.pid (Column) +u.uid - auth.users.uid (Column) +p.content - auth.posts.content (Column) +p.created_at - auth.posts.created_at (Column) +u.email - auth.users.email (Column) + +-------------- + +select u.id, auth.posts.content from auth.users u join auth.posts p on p.| +**Should only consider columns from auth.posts here.** + +Results: +pid - auth.posts.pid (Column) +content - auth.posts.content (Column) +created_at - auth.posts.created_at (Column) +title - auth.posts.title (Column) +user_id - auth.posts.user_id (Column) + +-------------- + +select u.id, auth.posts.content from auth.users u join auth.posts p on p.u| +**Should only consider columns from auth.posts here.** + +Results: +user_id - auth.posts.user_id (Column) + +-------------- + +select u.id, auth.posts.content from auth.users u join auth.posts p on p.user_id | +select u.id, auth.posts.content from auth.users u join auth.posts p on p.user_id = | + +Results: +p.pid - auth.posts.pid (Column) +u.uid - auth.users.uid (Column) +p.content - auth.posts.content (Column) +p.created_at - auth.posts.created_at (Column) +u.email - auth.users.email (Column) + +-------------- + +select u.id, auth.posts.content from auth.users u join auth.posts p on p.user_id = u.| +**Should only consider columns from auth.users here.** + +Results: +uid - auth.users.uid (Column) +email - auth.users.email (Column) +name - auth.users.name (Column) + +-------------- + +select u.id, auth.posts.content from auth.users u join auth.posts p on p.user_id = u.u| +**Should only consider columns from auth.users here.** + +Results: +uid - auth.users.uid (Column) + +-------------- + +select u.id, auth.posts.content from auth.users u join auth.posts p on p.user_id = u.uid; | diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns.snap new file mode 100644 index 000000000..4adad6c64 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns.snap @@ -0,0 +1,118 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema if not exists private; + +create table private.users ( + id serial primary key, + email text unique not null, + name text not null, + "quoted_column" text +); + + +-------------- + +s| +select | + +Results: +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) +pg_catalog.RI_FKey_cascade_del() - Schema: pg_catalog.RI_FKey_cascade_del (Function) + +-------------- + +select "| + +Results: +email" - private.users.email (Column) +id" - private.users.id (Column) +name" - private.users.name (Column) +quoted_column" - private.users.quoted_column (Column) +abbrev" - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +select "|" + +Results: +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +select "e|" + +Results: +email - private.users.email (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) +elem_count_histogram - pg_catalog.pg_stats.elem_count_histogram (Column) +encoding - pg_catalog.pg_database.encoding (Column) + +-------------- + +select "email" | +select "email" f| +select "email" from | + +Results: +private - private (Schema) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) +private.users - private.users (Table) + +-------------- + +select "email" from "| + +Results: +private - private (Schema) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) +private"."users" - private.users (Table) + +-------------- + +select "email" from "|" + +Results: +private - private (Schema) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) +private"."users - private.users (Table) + +-------------- + +select "email" from "p|" + +Results: +public - public (Schema) +private - private (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) +information_schema"."parameters - information_schema.parameters (Table) + +-------------- + +select "email" from "private".| + +Results: +users - private.users (Table) + +-------------- + +select "email" from "private".u| +select "email" from "private".users; | diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns_with_aliases.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns_with_aliases.snap new file mode 100644 index 000000000..20c8bc5af --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns_with_aliases.snap @@ -0,0 +1,476 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema if not exists private; + +create table private.users ( + id serial primary key, + email text unique not null, + name text not null, + "quoted_column" text +); + +create table public.names ( + uid serial references private.users(id), + name text +); + + +-------------- + +***Case 1:*** + +s| +select | + +Results: +name - public.names.name (Column) +uid - public.names.uid (Column) +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) + +-------------- + +select "| + +Results: +name" - public.names.name (Column) +uid" - public.names.uid (Column) +email" - private.users.email (Column) +id" - private.users.id (Column) +name" - private.users.name (Column) + +-------------- + +select "|" + +Results: +name - public.names.name (Column) +uid - public.names.uid (Column) +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) + +-------------- + +select "p|" + +Results: +pad_attribute - information_schema.collations.pad_attribute (Column) +page - pg_catalog.pg_locks.page (Column) +pageno - pg_catalog.pg_largeobject.pageno (Column) +paracl - pg_catalog.pg_parameter_acl.paracl (Column) +parameter_default - information_schema.parameters.parameter_default (Column) + +-------------- + +select "pr".| +select "pr"."| +select "pr"."|" +select "pr"."e|" +select "pr"."email" | +select "pr"."email" f| +select "pr"."email" from | + +Results: +names - public.names (Table) +private - private (Schema) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) + +-------------- + +select "pr"."email" from p| + +Results: +public - public (Schema) +private - private (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) +information_schema.parameters - information_schema.parameters (Table) + +-------------- + +select "pr"."email" from private.| + +Results: +users - private.users (Table) + +-------------- + +select "pr"."email" from private.u| + +Results: +users - private.users (Table) + +-------------- + +select "pr"."email" from private.users | +select "pr"."email" from private.users "| +select "pr"."email" from private.users "|" +select "pr"."email" from private.users "p|" +select "pr"."email" from private.users "pr" | + + + + +***Case 2:*** + +s| from private.users "pr" +select | from private.users "pr" + +Results: +"pr".email - private.users.email (Column) +"pr".id - private.users.id (Column) +"pr".name - private.users.name (Column) +"pr".quoted_column - private.users.quoted_column (Column) +name - public.names.name (Column) + +-------------- + +select "| from private.users "pr" + +Results: +pr"."email" - private.users.email (Column) +pr"."id" - private.users.id (Column) +pr"."name" - private.users.name (Column) +pr"."quoted_column" - private.users.quoted_column (Column) +name" - public.names.name (Column) + +-------------- + +select "|" from private.users "pr" + +Results: +pr"."email - private.users.email (Column) +pr"."id - private.users.id (Column) +pr"."name - private.users.name (Column) +pr"."quoted_column - private.users.quoted_column (Column) +name - public.names.name (Column) + +-------------- + +select "e|" from private.users "pr" + +Results: +pr"."email - private.users.email (Column) +pr"."name - private.users.name (Column) +pr"."quoted_column - private.users.quoted_column (Column) +name - public.names.name (Column) +elem_count_histogram - pg_catalog.pg_stats.elem_count_histogram (Column) + +-------------- + +select "email" | from private.users "pr" + + + + +***Case 3:*** + +s| from private.users "pr" +select | from private.users "pr" + +Results: +"pr".email - private.users.email (Column) +"pr".id - private.users.id (Column) +"pr".name - private.users.name (Column) +"pr".quoted_column - private.users.quoted_column (Column) +name - public.names.name (Column) + +-------------- + +select p| from private.users "pr" + +Results: +"pr".email - private.users.email (Column) +"pr".id - private.users.id (Column) +"pr".name - private.users.name (Column) +"pr".quoted_column - private.users.quoted_column (Column) +pad_attribute - information_schema.collations.pad_attribute (Column) + +-------------- + +select pr.| from private.users "pr" + +Results: +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select pr."| from private.users "pr" + +Results: +email" - private.users.email (Column) +id" - private.users.id (Column) +name" - private.users.name (Column) +quoted_column" - private.users.quoted_column (Column) + +-------------- + +select pr."|" from private.users "pr" + +Results: +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select pr."e|" from private.users "pr" + +Results: +email - private.users.email (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select pr."email" | from private.users "pr" + + + + +***Case 4:*** + +s| from private.users "pr" +select | from private.users "pr" + +Results: +"pr".email - private.users.email (Column) +"pr".id - private.users.id (Column) +"pr".name - private.users.name (Column) +"pr".quoted_column - private.users.quoted_column (Column) +name - public.names.name (Column) + +-------------- + +select "| from private.users "pr" + +Results: +pr"."email" - private.users.email (Column) +pr"."id" - private.users.id (Column) +pr"."name" - private.users.name (Column) +pr"."quoted_column" - private.users.quoted_column (Column) +name" - public.names.name (Column) + +-------------- + +select "|" from private.users "pr" + +Results: +pr"."email - private.users.email (Column) +pr"."id - private.users.id (Column) +pr"."name - private.users.name (Column) +pr"."quoted_column - private.users.quoted_column (Column) +name - public.names.name (Column) + +-------------- + +select "p|" from private.users "pr" + +Results: +pr"."email - private.users.email (Column) +pr"."id - private.users.id (Column) +pr"."name - private.users.name (Column) +pr"."quoted_column - private.users.quoted_column (Column) +pad_attribute - information_schema.collations.pad_attribute (Column) + +-------------- + +select "pr".| from private.users "pr" + +Results: +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select "pr"."| from private.users "pr" + +Results: +email" - private.users.email (Column) +id" - private.users.id (Column) +name" - private.users.name (Column) +quoted_column" - private.users.quoted_column (Column) + +-------------- + +select "pr"."|" from private.users "pr" + +Results: +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select "pr"."e|" from private.users "pr" + +Results: +email - private.users.email (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select "pr"."email" | from private.users "pr" + + + + +***Case 5:*** + +s| from private.users "pr" +select | from private.users "pr" + +Results: +"pr".email - private.users.email (Column) +"pr".id - private.users.id (Column) +"pr".name - private.users.name (Column) +"pr".quoted_column - private.users.quoted_column (Column) +name - public.names.name (Column) + +-------------- + +select p| from private.users "pr" + +Results: +"pr".email - private.users.email (Column) +"pr".id - private.users.id (Column) +"pr".name - private.users.name (Column) +"pr".quoted_column - private.users.quoted_column (Column) +pad_attribute - information_schema.collations.pad_attribute (Column) + +-------------- + +select pr.| from private.users "pr" +**not quoted here, since the alias isn't.** + +Results: +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select pr.e| from private.users "pr" + +Results: +email - private.users.email (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select pr.email | from private.users "pr" + + + + +***Case 6:*** + +s| from private.users "pr" join public.names n on pr.id = n.uid; +select | from private.users "pr" join public.names n on pr.id = n.uid; + +Results: +n.name - public.names.name (Column) +n.uid - public.names.uid (Column) +"pr".email - private.users.email (Column) +"pr".id - private.users.id (Column) +"pr".name - private.users.name (Column) + +-------------- + +select "| from private.users "pr" join public.names n on pr.id = n.uid; + +Results: +n"."name" - public.names.name (Column) +n"."uid" - public.names.uid (Column) +pr"."email" - private.users.email (Column) +pr"."id" - private.users.id (Column) +pr"."name" - private.users.name (Column) + +-------------- + +select "|" from private.users "pr" join public.names n on pr.id = n.uid; + +Results: +n"."name - public.names.name (Column) +n"."uid - public.names.uid (Column) +pr"."email - private.users.email (Column) +pr"."id - private.users.id (Column) +pr"."name - private.users.name (Column) + +-------------- + +select "p|" from private.users "pr" join public.names n on pr.id = n.uid; + +Results: +pr"."email - private.users.email (Column) +pr"."id - private.users.id (Column) +pr"."name - private.users.name (Column) +pr"."quoted_column - private.users.quoted_column (Column) +pad_attribute - information_schema.collations.pad_attribute (Column) + +-------------- + +select "pr".| from private.users "pr" join public.names n on pr.id = n.uid; + +Results: +email - private.users.email (Column) +id - private.users.id (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select "pr".e| from private.users "pr" join public.names n on pr.id = n.uid; + +Results: +email - private.users.email (Column) +name - private.users.name (Column) +quoted_column - private.users.quoted_column (Column) + +-------------- + +select "pr"."email", | from private.users "pr" join public.names n on pr.id = n.uid; + +Results: +n.name - public.names.name (Column) +n.uid - public.names.uid (Column) +"pr".id - private.users.id (Column) +"pr".name - private.users.name (Column) +"pr".quoted_column - private.users.quoted_column (Column) + +-------------- + +select "pr"."email", n.| from private.users "pr" join public.names n on pr.id = n.uid; + +Results: +name - public.names.name (Column) +uid - public.names.uid (Column) + +-------------- + +select "pr"."email", n.u| from private.users "pr" join public.names n on pr.id = n.uid; + +Results: +uid - public.names.uid (Column) + +-------------- + +select "pr"."email", n.uid | from private.users "pr" join public.names n on pr.id = n.uid; diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__does_not_complete_cols_in_join_clauses.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__does_not_complete_cols_in_join_clauses.snap new file mode 100644 index 000000000..7aa565e5f --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__does_not_complete_cols_in_join_clauses.snap @@ -0,0 +1,196 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema auth; + +create table auth.users ( + uid serial primary key, + name text not null, + email text unique not null +); + +create table auth.posts ( + pid serial primary key, + user_id int not null references auth.users(uid), + title text not null, + content text, + created_at timestamp default now() +); + + +-------------- + +s| +select | + +Results: +content - auth.posts.content (Column) +created_at - auth.posts.created_at (Column) +email - auth.users.email (Column) +name - auth.users.name (Column) +pid - auth.posts.pid (Column) + +-------------- + +select u.| +select u.u| +select u.uid, | + +Results: +content - auth.posts.content (Column) +created_at - auth.posts.created_at (Column) +email - auth.users.email (Column) +name - auth.users.name (Column) +pid - auth.posts.pid (Column) + +-------------- + +select u.uid, p.| +select u.uid, p.c| +select u.uid, p.content | +select u.uid, p.content f| +select u.uid, p.content from | +**Schema suggestions should be prioritized, since we want to push users to specify them.** + +Results: +public - public (Schema) +auth - auth (Schema) +auth.posts - auth.posts (Table) +auth.users - auth.users (Table) +information_schema - information_schema (Schema) + +-------------- + +select u.uid, p.content from a| +**Schema suggestions should be prioritized, since we want to push users to specify them.** + +Results: +auth - auth (Schema) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) +information_schema.administrable_role_authorizations - information_schema.administrable_role_authorizations (Table) + +-------------- + +select u.uid, p.content from auth.| +**Here, we shouldn't have schema completions.** + +Results: +posts - auth.posts (Table) +users - auth.users (Table) + +-------------- + +select u.uid, p.content from auth.u| +**Here, we shouldn't have schema completions.** + +Results: +users - auth.users (Table) + +-------------- + +select u.uid, p.content from auth.users | +select u.uid, p.content from auth.users u | +select u.uid, p.content from auth.users u j| +select u.uid, p.content from auth.users u join | + +Results: +public - public (Schema) +auth - auth (Schema) +auth.posts - auth.posts (Table) +auth.users - auth.users (Table) +information_schema - information_schema (Schema) + +-------------- + +select u.uid, p.content from auth.users u join a| + +Results: +auth - auth (Schema) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) +information_schema.administrable_role_authorizations - information_schema.administrable_role_authorizations (Table) + +-------------- + +select u.uid, p.content from auth.users u join auth.| + +Results: +posts - auth.posts (Table) +users - auth.users (Table) + +-------------- + +select u.uid, p.content from auth.users u join auth.p| + +Results: +posts - auth.posts (Table) + +-------------- + +select u.uid, p.content from auth.users u join auth.posts | +select u.uid, p.content from auth.users u join auth.posts p | +select u.uid, p.content from auth.users u join auth.posts p o| +select u.uid, p.content from auth.users u join auth.posts p on | + +Results: +p.pid - auth.posts.pid (Column) +u.uid - auth.users.uid (Column) +p.content - auth.posts.content (Column) +p.created_at - auth.posts.created_at (Column) +u.email - auth.users.email (Column) + +-------------- + +select u.uid, p.content from auth.users u join auth.posts p on u.| + +Results: +uid - auth.users.uid (Column) +email - auth.users.email (Column) +name - auth.users.name (Column) + +-------------- + +select u.uid, p.content from auth.users u join auth.posts p on u.u| + +Results: +uid - auth.users.uid (Column) + +-------------- + +select u.uid, p.content from auth.users u join auth.posts p on u.uid | +select u.uid, p.content from auth.users u join auth.posts p on u.uid = | + +Results: +p.pid - auth.posts.pid (Column) +u.uid - auth.users.uid (Column) +p.content - auth.posts.content (Column) +p.created_at - auth.posts.created_at (Column) +u.email - auth.users.email (Column) + +-------------- + +select u.uid, p.content from auth.users u join auth.posts p on u.uid = p.| + +Results: +pid - auth.posts.pid (Column) +content - auth.posts.content (Column) +created_at - auth.posts.created_at (Column) +title - auth.posts.title (Column) +user_id - auth.posts.user_id (Column) + +-------------- + +select u.uid, p.content from auth.users u join auth.posts p on u.uid = p.u| + +Results: +user_id - auth.posts.user_id (Column) + +-------------- + +select u.uid, p.content from auth.users u join auth.posts p on u.uid = p.user_id | diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_join_on.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_join_on.snap new file mode 100644 index 000000000..37c635810 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_join_on.snap @@ -0,0 +1,88 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema auth; + +create table auth.users ( + uid serial primary key, + name text not null, + email text unique not null +); + +create table auth.posts ( + pid serial primary key, + user_id int not null references auth.users(uid), + title text not null, + content text, + created_at timestamp default now() +); + + +-------------- + +select u.id, p.content from auth.users u join auth.posts p | +select u.id, p.content from auth.users u join auth.posts p o| +select u.id, p.content from auth.users u join auth.posts p on | +**Should prefer primary indices here.** + +Results: +p.pid - auth.posts.pid (Column) +u.uid - auth.users.uid (Column) +p.content - auth.posts.content (Column) +p.created_at - auth.posts.created_at (Column) +u.email - auth.users.email (Column) + +-------------- + +select u.id, p.content from auth.users u join auth.posts p on u.| + +Results: +uid - auth.users.uid (Column) +email - auth.users.email (Column) +name - auth.users.name (Column) + +-------------- + +select u.id, p.content from auth.users u join auth.posts p on u.i| + +Results: +uid - auth.users.uid (Column) +email - auth.users.email (Column) + +-------------- + +select u.id, p.content from auth.users u join auth.posts p on u.id | +select u.id, p.content from auth.users u join auth.posts p on u.id = | + +Results: +p.pid - auth.posts.pid (Column) +u.uid - auth.users.uid (Column) +p.content - auth.posts.content (Column) +p.created_at - auth.posts.created_at (Column) +u.email - auth.users.email (Column) + +-------------- + +select u.id, p.content from auth.users u join auth.posts p on u.id = p.| +**We should only get columns from the auth.posts table.** + +Results: +pid - auth.posts.pid (Column) +content - auth.posts.content (Column) +created_at - auth.posts.created_at (Column) +title - auth.posts.title (Column) +user_id - auth.posts.user_id (Column) + +-------------- + +select u.id, p.content from auth.users u join auth.posts p on u.id = p.u| + +Results: +user_id - auth.posts.user_id (Column) + +-------------- + +select u.id, p.content from auth.users u join auth.posts p on u.id = p.user_id | diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_select.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_select.snap new file mode 100644 index 000000000..0a80debe3 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_select.snap @@ -0,0 +1,86 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema auth; + +create table auth.users ( + uid serial primary key, + name text not null, + email text unique not null +); + +create table auth.posts ( + pid serial primary key, + user_id int not null references auth.users(uid), + title text not null, + content text, + created_at timestamp default now() +); + + +-------------- + +s| from auth.users u join auth.posts p on u.id = p.user_id; +select | from auth.users u join auth.posts p on u.id = p.user_id; + +Results: +p.content - auth.posts.content (Column) +p.created_at - auth.posts.created_at (Column) +u.email - auth.users.email (Column) +u.name - auth.users.name (Column) +p.pid - auth.posts.pid (Column) + +-------------- + +select u.| from auth.users u join auth.posts p on u.id = p.user_id; + +Results: +email - auth.users.email (Column) +name - auth.users.name (Column) +uid - auth.users.uid (Column) + +-------------- + +select u.i| from auth.users u join auth.posts p on u.id = p.user_id; + +Results: +email - auth.users.email (Column) +uid - auth.users.uid (Column) + +-------------- + +select u.id, | from auth.users u join auth.posts p on u.id = p.user_id; + +Results: +p.content - auth.posts.content (Column) +p.created_at - auth.posts.created_at (Column) +u.email - auth.users.email (Column) +u.name - auth.users.name (Column) +p.pid - auth.posts.pid (Column) + +-------------- + +select u.id, p.| from auth.users u join auth.posts p on u.id = p.user_id; +**We should only get columns from the auth.posts table.** + +Results: +content - auth.posts.content (Column) +created_at - auth.posts.created_at (Column) +pid - auth.posts.pid (Column) +title - auth.posts.title (Column) +user_id - auth.posts.user_id (Column) + +-------------- + +select u.id, p.p| from auth.users u join auth.posts p on u.id = p.user_id; +**We should only get columns from the auth.posts table.** + +Results: +pid - auth.posts.pid (Column) + +-------------- + +select u.id, p.pid | from auth.users u join auth.posts p on u.id = p.user_id; diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__handles_nested_queries.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__handles_nested_queries.snap new file mode 100644 index 000000000..bbef4c4d4 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__handles_nested_queries.snap @@ -0,0 +1,165 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema private; + +create table public.users ( + id serial primary key, + name text +); + +create table public.audio_books ( + id serial primary key, + narrator text +); + +create table private.audio_books ( + id serial primary key, + narrator_id text +); + + +-------------- + +select * from ( + | +) as subquery +join public.users u +on u.id = subquery.id; +select * from ( + s| +) as subquery +join public.users u +on u.id = subquery.id; +select * from ( + select | +) as subquery +join public.users u +on u.id = subquery.id; + +Results: +pg_catalog.RI_FKey_cascade_del() - Schema: pg_catalog.RI_FKey_cascade_del (Function) +pg_catalog.RI_FKey_cascade_upd() - Schema: pg_catalog.RI_FKey_cascade_upd (Function) +pg_catalog.RI_FKey_check_ins() - Schema: pg_catalog.RI_FKey_check_ins (Function) +pg_catalog.RI_FKey_check_upd() - Schema: pg_catalog.RI_FKey_check_upd (Function) +pg_catalog.RI_FKey_noaction_del() - Schema: pg_catalog.RI_FKey_noaction_del (Function) + +-------------- + +select * from ( + select i| +) as subquery +join public.users u +on u.id = subquery.id; + +Results: +iclikejoinsel - Schema: pg_catalog.iclikejoinsel (Function) +iclikesel - Schema: pg_catalog.iclikesel (Function) +icnlikejoinsel - Schema: pg_catalog.icnlikejoinsel (Function) +icnlikesel - Schema: pg_catalog.icnlikesel (Function) +icregexeqjoinsel - Schema: pg_catalog.icregexeqjoinsel (Function) + +-------------- + +select * from ( + select id, | +) as subquery +join public.users u +on u.id = subquery.id; +**Should prefer the one from private.audio_audiobooks, since the other tables are out of scope.** + +Results: +pg_catalog.RI_FKey_cascade_del() - Schema: pg_catalog.RI_FKey_cascade_del (Function) +pg_catalog.RI_FKey_cascade_upd() - Schema: pg_catalog.RI_FKey_cascade_upd (Function) +pg_catalog.RI_FKey_check_ins() - Schema: pg_catalog.RI_FKey_check_ins (Function) +pg_catalog.RI_FKey_check_upd() - Schema: pg_catalog.RI_FKey_check_upd (Function) +pg_catalog.RI_FKey_noaction_del() - Schema: pg_catalog.RI_FKey_noaction_del (Function) + +-------------- + +select * from ( + select id, n| +) as subquery +join public.users u +on u.id = subquery.id; +**Should prefer the one from private.audio_audiobooks, since the other tables are out of scope.** + +Results: +name - Schema: pg_catalog.name (Function) +nameconcatoid - Schema: pg_catalog.nameconcatoid (Function) +nameeq - Schema: pg_catalog.nameeq (Function) +nameeqtext - Schema: pg_catalog.nameeqtext (Function) +namege - Schema: pg_catalog.namege (Function) + +-------------- + +select * from ( + select id, narrator_id | +) as subquery +join public.users u +on u.id = subquery.id; +select * from ( + select id, narrator_id f| +) as subquery +join public.users u +on u.id = subquery.id; +select * from ( + select id, narrator_id from | +) as subquery +join public.users u +on u.id = subquery.id; + +Results: +public - public (Schema) +private - private (Schema) +audio_books - public.audio_books (Table) +users - public.users (Table) +private.audio_books - private.audio_books (Table) + +-------------- + +select * from ( + select id, narrator_id from p| +) as subquery +join public.users u +on u.id = subquery.id; + +Results: +public - public (Schema) +private - private (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) +information_schema.parameters - information_schema.parameters (Table) + +-------------- + +select * from ( + select id, narrator_id from private.| +) as subquery +join public.users u +on u.id = subquery.id; + +Results: +audio_books - private.audio_books (Table) + +-------------- + +select * from ( + select id, narrator_id from private.a| +) as subquery +join public.users u +on u.id = subquery.id; + +Results: +audio_books - private.audio_books (Table) + +-------------- + +select * from ( + select id, narrator_id from private.audio_books | +) as subquery +join public.users u +on u.id = subquery.id; diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__ignores_cols_in_from_clause.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__ignores_cols_in_from_clause.snap new file mode 100644 index 000000000..ddd575abb --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__ignores_cols_in_from_clause.snap @@ -0,0 +1,58 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema private; + +create table private.users ( + id serial primary key, + name text, + address text, + email text, + public boolean +); + + +-------------- + +select * from | +**No column suggestions.** + +Results: +public - public (Schema) +private - private (Schema) +private.users - private.users (Table) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) + +-------------- + +select * from p| +**No column suggestions.** + +Results: +public - public (Schema) +private - private (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) +information_schema.parameters - information_schema.parameters (Table) + +-------------- + +select * from private.| + +Results: +users - private.users (Table) + +-------------- + +select * from private.u| + +Results: +users - private.users (Table) + +-------------- + +select * from private.users | diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_columns_of_mentioned_tables.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_columns_of_mentioned_tables.snap new file mode 100644 index 000000000..ca024d628 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_columns_of_mentioned_tables.snap @@ -0,0 +1,119 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema private; + +create table private.users ( + id1 serial primary key, + name1 text, + address1 text, + email1 text, + user_settings jsonb +); + +create table public.users ( + id2 serial primary key, + name2 text, + address2 text, + email2 text, + settings jsonb +); + + +-------------- + +***Case 1:*** + +s| from public.users +select | from public.users +**Should suggest address 2 from public table** + +Results: +address2 - public.users.address2 (Column) +email2 - public.users.email2 (Column) +id2 - public.users.id2 (Column) +name2 - public.users.name2 (Column) +settings - public.users.settings (Column) + +-------------- + +select a| from public.users +**Should suggest address 2 from public table** + +Results: +address2 - public.users.address2 (Column) +email2 - public.users.email2 (Column) +name2 - public.users.name2 (Column) +address1 - private.users.address1 (Column) +email1 - private.users.email1 (Column) + +-------------- + +select address2 | from public.users + + + + +***Case 2:*** + +s| from private.users +select | from private.users +**Should suggest address 1 from private table** + +Results: +address1 - private.users.address1 (Column) +email1 - private.users.email1 (Column) +id1 - private.users.id1 (Column) +name1 - private.users.name1 (Column) +user_settings - private.users.user_settings (Column) + +-------------- + +select a| from private.users +**Should suggest address 1 from private table** + +Results: +address1 - private.users.address1 (Column) +email1 - private.users.email1 (Column) +name1 - private.users.name1 (Column) +address2 - public.users.address2 (Column) +email2 - public.users.email2 (Column) + +-------------- + +select address1 | from private.users + + + + +***Case 3:*** + +s| from private.users +select | from private.users +**Should prioritize columns starting with s** + +Results: +address1 - private.users.address1 (Column) +email1 - private.users.email1 (Column) +id1 - private.users.id1 (Column) +name1 - private.users.name1 (Column) +user_settings - private.users.user_settings (Column) + +-------------- + +select s| from private.users +**Should prioritize columns starting with s** + +Results: +user_settings - private.users.user_settings (Column) +address1 - private.users.address1 (Column) +settings - public.users.settings (Column) +address2 - public.users.address2 (Column) +safe_wal_size - pg_catalog.pg_replication_slots.safe_wal_size (Column) + +-------------- + +select settings | from private.users diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_not_mentioned_columns.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_not_mentioned_columns.snap new file mode 100644 index 000000000..52b19b693 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_not_mentioned_columns.snap @@ -0,0 +1,332 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema auth; + +create table public.one ( + id serial primary key, + a text, + b text, + z text +); + +create table public.two ( + id serial primary key, + c text, + d text, + e text +); + + +-------------- + +***Case 1:*** + +s| from public.one o join public.two on o.id = t.id; +select | from public.one o join public.two on o.id = t.id; + +Results: +o.a - public.one.a (Column) +o.b - public.one.b (Column) +c - public.two.c (Column) +d - public.two.d (Column) +e - public.two.e (Column) + +-------------- + +select o.| from public.one o join public.two on o.id = t.id; + +Results: +a - public.one.a (Column) +b - public.one.b (Column) +id - public.one.id (Column) +z - public.one.z (Column) + +-------------- + +select o.i| from public.one o join public.two on o.id = t.id; + +Results: +id - public.one.id (Column) + +-------------- + +select o.id, | from public.one o join public.two on o.id = t.id; + +Results: +o.a - public.one.a (Column) +o.b - public.one.b (Column) +c - public.two.c (Column) +d - public.two.d (Column) +e - public.two.e (Column) + +-------------- + +select o.id, a| from public.one o join public.two on o.id = t.id; + +Results: +o.a - public.one.a (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) +action_condition - information_schema.triggers.action_condition (Column) +action_order - information_schema.triggers.action_order (Column) +action_orientation - information_schema.triggers.action_orientation (Column) + +-------------- + +select o.id, a, | from public.one o join public.two on o.id = t.id; +**Should have low priority for `a`, since it's already mentioned.** + +Results: +o.b - public.one.b (Column) +c - public.two.c (Column) +d - public.two.d (Column) +e - public.two.e (Column) +id - public.two.id (Column) + +-------------- + +select o.id, a, b| from public.one o join public.two on o.id = t.id; + +Results: +o.b - public.one.b (Column) +backend_start - pg_catalog.pg_stat_activity.backend_start (Column) +backend_type - pg_catalog.pg_stat_activity.backend_type (Column) +backend_xid - pg_catalog.pg_stat_activity.backend_xid (Column) +backend_xmin - pg_catalog.pg_stat_activity.backend_xmin (Column) + +-------------- + +select o.id, a, b, | from public.one o join public.two on o.id = t.id; + +Results: +c - public.two.c (Column) +d - public.two.d (Column) +e - public.two.e (Column) +id - public.two.id (Column) +o.z - public.one.z (Column) + +-------------- + +select o.id, a, b, c| from public.one o join public.two on o.id = t.id; + +Results: +c - public.two.c (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +select o.id, a, b, c, | from public.one o join public.two on o.id = t.id; + +Results: +d - public.two.d (Column) +e - public.two.e (Column) +id - public.two.id (Column) +o.z - public.one.z (Column) +o.a - public.one.a (Column) + +-------------- + +select o.id, a, b, c, d| from public.one o join public.two on o.id = t.id; + +Results: +id - public.two.id (Column) +d - public.two.d (Column) +o.id - public.one.id (Column) +data - pg_catalog.pg_largeobject.data (Column) +data_type - pg_catalog.pg_sequences.data_type (Column) + +-------------- + +select o.id, a, b, c, d, | from public.one o join public.two on o.id = t.id; + +Results: +e - public.two.e (Column) +id - public.two.id (Column) +o.z - public.one.z (Column) +o.a - public.one.a (Column) +o.b - public.one.b (Column) + +-------------- + +select o.id, a, b, c, d, e| from public.one o join public.two on o.id = t.id; + +Results: +e - public.two.e (Column) +elem_count_histogram - pg_catalog.pg_stats.elem_count_histogram (Column) +encoding - pg_catalog.pg_database.encoding (Column) +encrypted - pg_catalog.pg_stat_gssapi.encrypted (Column) +enforced - information_schema.table_constraints.enforced (Column) + +-------------- + +select o.id, a, b, c, d, e, | from public.one o join public.two on o.id = t.id; +**Should have high priority of id of table two, but not one, since it's already mentioned.** + +Results: +id - public.two.id (Column) +o.z - public.one.z (Column) +o.a - public.one.a (Column) +o.b - public.one.b (Column) +c - public.two.c (Column) + +-------------- + +select o.id, a, b, c, d, e, z | from public.one o join public.two on o.id = t.id; + + + + +***Case 2:*** + +s| from public.one o join public.two on o.id = t.id; +select | from public.one o join public.two on o.id = t.id; + +Results: +o.a - public.one.a (Column) +o.b - public.one.b (Column) +c - public.two.c (Column) +d - public.two.d (Column) +e - public.two.e (Column) + +-------------- + +select i| from public.one o join public.two on o.id = t.id; + +Results: +id - public.two.id (Column) +ident - pg_catalog.pg_backend_memory_contexts.ident (Column) +identity_cycle - information_schema.columns.identity_cycle (Column) +identity_generation - information_schema.columns.identity_generation (Column) +identity_increment - information_schema.columns.identity_increment (Column) + +-------------- + +select id, | from public.one o join public.two on o.id = t.id; + +Results: +o.a - public.one.a (Column) +o.b - public.one.b (Column) +c - public.two.c (Column) +d - public.two.d (Column) +e - public.two.e (Column) + +-------------- + +select id, a| from public.one o join public.two on o.id = t.id; + +Results: +o.a - public.one.a (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) +action_condition - information_schema.triggers.action_condition (Column) +action_order - information_schema.triggers.action_order (Column) +action_orientation - information_schema.triggers.action_orientation (Column) + +-------------- + +select id, a, | from public.one o join public.two on o.id = t.id; + +Results: +o.b - public.one.b (Column) +c - public.two.c (Column) +d - public.two.d (Column) +e - public.two.e (Column) +o.z - public.one.z (Column) + +-------------- + +select id, a, b| from public.one o join public.two on o.id = t.id; + +Results: +o.b - public.one.b (Column) +backend_start - pg_catalog.pg_stat_activity.backend_start (Column) +backend_type - pg_catalog.pg_stat_activity.backend_type (Column) +backend_xid - pg_catalog.pg_stat_activity.backend_xid (Column) +backend_xmin - pg_catalog.pg_stat_activity.backend_xmin (Column) + +-------------- + +select id, a, b, | from public.one o join public.two on o.id = t.id; + +Results: +c - public.two.c (Column) +d - public.two.d (Column) +e - public.two.e (Column) +o.z - public.one.z (Column) +o.a - public.one.a (Column) + +-------------- + +select id, a, b, c| from public.one o join public.two on o.id = t.id; + +Results: +c - public.two.c (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +select id, a, b, c, | from public.one o join public.two on o.id = t.id; + +Results: +d - public.two.d (Column) +e - public.two.e (Column) +o.z - public.one.z (Column) +o.a - public.one.a (Column) +o.b - public.one.b (Column) + +-------------- + +select id, a, b, c, d| from public.one o join public.two on o.id = t.id; + +Results: +d - public.two.d (Column) +id - public.two.id (Column) +data - pg_catalog.pg_largeobject.data (Column) +data_type - pg_catalog.pg_sequences.data_type (Column) +database - pg_catalog.pg_hba_file_rules.database (Column) + +-------------- + +select id, a, b, c, d, | from public.one o join public.two on o.id = t.id; + +Results: +e - public.two.e (Column) +o.z - public.one.z (Column) +o.a - public.one.a (Column) +o.b - public.one.b (Column) +c - public.two.c (Column) + +-------------- + +select id, a, b, c, d, e| from public.one o join public.two on o.id = t.id; + +Results: +e - public.two.e (Column) +elem_count_histogram - pg_catalog.pg_stats.elem_count_histogram (Column) +encoding - pg_catalog.pg_database.encoding (Column) +encrypted - pg_catalog.pg_stat_gssapi.encrypted (Column) +enforced - information_schema.table_constraints.enforced (Column) + +-------------- + +select id, a, b, c, d, e, | from public.one o join public.two on o.id = t.id; +**`id` could be from both tables, so both priorities are lowered.** + +Results: +o.z - public.one.z (Column) +o.a - public.one.a (Column) +o.b - public.one.b (Column) +c - public.two.c (Column) +d - public.two.d (Column) + +-------------- + +select id, a, b, c, d, e, z | from public.one o join public.two on o.id = t.id; diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__shows_multiple_columns_if_no_relation_specified.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__shows_multiple_columns_if_no_relation_specified.snap new file mode 100644 index 000000000..463bca8f4 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__shows_multiple_columns_if_no_relation_specified.snap @@ -0,0 +1,52 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create schema private; + +create table public.users ( + id serial primary key, + name text +); + +create table public.audio_books ( + id serial primary key, + narrator text +); + +create table private.audio_books ( + id serial primary key, + narrator_id text +); + + +-------------- + +s| +select | +**Should suggest all columns with n first** + +Results: +id - public.audio_books.id (Column) +name - public.users.name (Column) +narrator - public.audio_books.narrator (Column) +id - private.audio_books.id (Column) +narrator_id - private.audio_books.narrator_id (Column) + +-------------- + +select n| +**Should suggest all columns with n first** + +Results: +name - public.users.name (Column) +narrator - public.audio_books.narrator (Column) +narrator_id - private.audio_books.narrator_id (Column) +name - Schema: pg_catalog.name (Function) +nameconcatoid - Schema: pg_catalog.nameconcatoid (Function) + +-------------- + +select narrator_id | diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_alter_table_and_drop_table.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_alter_table_and_drop_table.snap new file mode 100644 index 000000000..4853ab4ca --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_alter_table_and_drop_table.snap @@ -0,0 +1,750 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create table instruments ( + id bigint primary key generated always as identity, + name text not null, + z text, + created_at timestamp with time zone default now() +); + + +-------------- + +***Case 1:*** + +a| +alter | +alter t| +alter table | + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) +information_schema._pg_foreign_table_columns - information_schema._pg_foreign_table_columns (Table) + +-------------- + +alter table i| + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (Table) +pg_catalog.pg_ident_file_mappings - pg_catalog.pg_ident_file_mappings (Table) + +-------------- + +alter table instruments | +alter table instruments d| +alter table instruments drop | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments drop c| + +Results: +created_at - public.instruments.created_at (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +alter table instruments drop column | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments drop column n| + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +alter table instruments drop column name | + + + + +***Case 2:*** + +a| +alter | +alter t| +alter table | + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) +information_schema._pg_foreign_table_columns - information_schema._pg_foreign_table_columns (Table) + +-------------- + +alter table i| + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (Table) +pg_catalog.pg_ident_file_mappings - pg_catalog.pg_ident_file_mappings (Table) + +-------------- + +alter table instruments | +alter table instruments d| +alter table instruments drop | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments drop c| + +Results: +created_at - public.instruments.created_at (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +alter table instruments drop column | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments drop column n| + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +alter table instruments drop column name | + + + + +***Case 3:*** + +a| +alter | +alter t| +alter table | + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) +information_schema._pg_foreign_table_columns - information_schema._pg_foreign_table_columns (Table) + +-------------- + +alter table i| + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (Table) +pg_catalog.pg_ident_file_mappings - pg_catalog.pg_ident_file_mappings (Table) + +-------------- + +alter table instruments | +alter table instruments d| +alter table instruments drop | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments drop c| + +Results: +created_at - public.instruments.created_at (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +alter table instruments drop column | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments drop column i| + +Results: +id - public.instruments.id (Column) +ident - pg_catalog.pg_backend_memory_contexts.ident (Column) +identity_cycle - information_schema.columns.identity_cycle (Column) +identity_generation - information_schema.columns.identity_generation (Column) +identity_increment - information_schema.columns.identity_increment (Column) + +-------------- + +alter table instruments drop column if | +alter table instruments drop column if e| + +Results: +created_at - public.instruments.created_at (Column) +name - public.instruments.name (Column) +elem_count_histogram - pg_catalog.pg_stats.elem_count_histogram (Column) +encoding - pg_catalog.pg_database.encoding (Column) +encrypted - pg_catalog.pg_stat_gssapi.encrypted (Column) + +-------------- + +alter table instruments drop column if exists | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments drop column if exists n| + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +alter table instruments drop column if exists name | + + + + +***Case 4:*** + +a| +alter | +alter t| +alter table | + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) +information_schema._pg_foreign_table_columns - information_schema._pg_foreign_table_columns (Table) + +-------------- + +alter table i| + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (Table) +pg_catalog.pg_ident_file_mappings - pg_catalog.pg_ident_file_mappings (Table) + +-------------- + +alter table instruments | +alter table instruments a| +alter table instruments alter | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments alter c| + +Results: +created_at - public.instruments.created_at (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +alter table instruments alter column | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments alter column n| + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +alter table instruments alter column name | +alter table instruments alter column name s| +alter table instruments alter column name set | +alter table instruments alter column name set d| +alter table instruments alter column name set default | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + + + + + +***Case 5:*** + +a| +alter | +alter t| +alter table | + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) +information_schema._pg_foreign_table_columns - information_schema._pg_foreign_table_columns (Table) + +-------------- + +alter table i| + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (Table) +pg_catalog.pg_ident_file_mappings - pg_catalog.pg_ident_file_mappings (Table) + +-------------- + +alter table instruments | +alter table instruments a| +alter table instruments alter | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments alter n| + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +alter table instruments alter name | +alter table instruments alter name s| +alter table instruments alter name set | +alter table instruments alter name set d| +alter table instruments alter name set default | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + + + + + +***Case 6:*** + +a| +alter | +alter t| +alter table | + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) +information_schema._pg_foreign_table_columns - information_schema._pg_foreign_table_columns (Table) + +-------------- + +alter table p| + +Results: +public - public (Schema) +information_schema.parameters - information_schema.parameters (Table) +pg_catalog.pg_aggregate - pg_catalog.pg_aggregate (Table) +pg_catalog.pg_am - pg_catalog.pg_am (Table) +pg_catalog.pg_amop - pg_catalog.pg_amop (Table) + +-------------- + +alter table public.| + +Results: +instruments - public.instruments (Table) +_sqlx_migrations - public._sqlx_migrations (Table) + +-------------- + +alter table public.i| + +Results: +instruments - public.instruments (Table) +_sqlx_migrations - public._sqlx_migrations (Table) + +-------------- + +alter table public.instruments | +alter table public.instruments a| +alter table public.instruments alter | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table public.instruments alter c| + +Results: +created_at - public.instruments.created_at (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +alter table public.instruments alter column | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table public.instruments alter column n| + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +alter table public.instruments alter column name | + + + + +***Case 7:*** + +a| +alter | +alter t| +alter table | + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) +information_schema._pg_foreign_table_columns - information_schema._pg_foreign_table_columns (Table) + +-------------- + +alter table i| + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (Table) +pg_catalog.pg_ident_file_mappings - pg_catalog.pg_ident_file_mappings (Table) + +-------------- + +alter table instruments | +alter table instruments a| +alter table instruments alter | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments alter n| + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +alter table instruments alter name | + + + + +***Case 8:*** + +a| +alter | +alter t| +alter table | + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) +information_schema._pg_foreign_table_columns - information_schema._pg_foreign_table_columns (Table) + +-------------- + +alter table i| + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (Table) +pg_catalog.pg_ident_file_mappings - pg_catalog.pg_ident_file_mappings (Table) + +-------------- + +alter table instruments | +alter table instruments r| +alter table instruments rename | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table instruments rename n| + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +alter table instruments rename name | +alter table instruments rename name t| +alter table instruments rename name to | +alter table instruments rename name to n| +alter table instruments rename name to new_col | + + + + +***Case 9:*** + +a| +alter | +alter t| +alter table | + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) +information_schema._pg_foreign_table_columns - information_schema._pg_foreign_table_columns (Table) + +-------------- + +alter table p| + +Results: +public - public (Schema) +information_schema.parameters - information_schema.parameters (Table) +pg_catalog.pg_aggregate - pg_catalog.pg_aggregate (Table) +pg_catalog.pg_am - pg_catalog.pg_am (Table) +pg_catalog.pg_amop - pg_catalog.pg_amop (Table) + +-------------- + +alter table public.| + +Results: +instruments - public.instruments (Table) +_sqlx_migrations - public._sqlx_migrations (Table) + +-------------- + +alter table public.i| + +Results: +instruments - public.instruments (Table) +_sqlx_migrations - public._sqlx_migrations (Table) + +-------------- + +alter table public.instruments | +alter table public.instruments r| +alter table public.instruments rename | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table public.instruments rename c| + +Results: +created_at - public.instruments.created_at (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +alter table public.instruments rename column | + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +alter table public.instruments rename column n| + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +alter table public.instruments rename column name | +alter table public.instruments rename column name t| +alter table public.instruments rename column name to | +alter table public.instruments rename column name to n| +alter table public.instruments rename column name to new_col | diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_insert_clause.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_insert_clause.snap new file mode 100644 index 000000000..b7dcd79a5 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_insert_clause.snap @@ -0,0 +1,282 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create table instruments ( + id bigint primary key generated always as identity, + name text not null, + z text +); + +create table others ( + id serial primary key, + a text, + b text +); + + +-------------- + +***Case 1:*** + +i| +insert | +insert i| +insert into | + +Results: +instruments - public.instruments (Table) +others - public.others (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) + +-------------- + +insert into i| + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (Table) +pg_catalog.pg_ident_file_mappings - pg_catalog.pg_ident_file_mappings (Table) + +-------------- + +insert into instruments | +insert into instruments (| +insert into instruments (|) + +Results: +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments (i|) + +Results: +id - public.instruments.id (Column) +ident - pg_catalog.pg_backend_memory_contexts.ident (Column) +identity_cycle - information_schema.columns.identity_cycle (Column) +identity_generation - information_schema.columns.identity_generation (Column) +identity_increment - information_schema.columns.identity_increment (Column) + +-------------- + +insert into instruments (id, |) + +Results: +name - public.instruments.name (Column) +z - public.instruments.z (Column) +id - public.instruments.id (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments (id, n|) + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +insert into instruments (id, name) | +insert into instruments (id, name) v| +insert into instruments (id, name) values | +insert into instruments (id, name) values (| +insert into instruments (id, name) values (|) + +Results: +z - public.instruments.z (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments (id, name) values (1|) +insert into instruments (id, name) values (1, |) + +Results: +z - public.instruments.z (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments (id, name) values (1, '|) +insert into instruments (id, name) values (1, 'my_bass'); | + + + + +***Case 2:*** + +i| +insert | +insert i| +insert into | + +Results: +instruments - public.instruments (Table) +others - public.others (Table) +public - public (Schema) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) + +-------------- + +insert into i| + +Results: +instruments - public.instruments (Table) +public - public (Schema) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (Table) +pg_catalog.pg_ident_file_mappings - pg_catalog.pg_ident_file_mappings (Table) + +-------------- + +insert into instruments | +insert into instruments (| +insert into instruments (|) + +Results: +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments (i|) + +Results: +id - public.instruments.id (Column) +ident - pg_catalog.pg_backend_memory_contexts.ident (Column) +identity_cycle - information_schema.columns.identity_cycle (Column) +identity_generation - information_schema.columns.identity_generation (Column) +identity_increment - information_schema.columns.identity_increment (Column) + +-------------- + +insert into instruments ("id", |) + +Results: +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments ("id", "|) + +Results: +id" - public.instruments.id (Column) +name" - public.instruments.name (Column) +z" - public.instruments.z (Column) +a" - public.others.a (Column) +b" - public.others.b (Column) + +-------------- + +insert into instruments ("id", "|") + +Results: +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments ("id", "n|") + +Results: +name - public.instruments.name (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_tup (Column) +n_distinct - pg_catalog.pg_stats.n_distinct (Column) +n_ins_since_vacuum - pg_catalog.pg_stat_all_tables.n_ins_since_vacuum (Column) +n_live_tup - pg_catalog.pg_stat_all_tables.n_live_tup (Column) + +-------------- + +insert into instruments ("id", "name") | +insert into instruments ("id", "name") v| +insert into instruments ("id", "name") values | +insert into instruments ("id", "name") values (| +insert into instruments ("id", "name") values (|) + +Results: +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments ("id", "name") values (1|) +insert into instruments ("id", "name") values (1, |) + +Results: +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments ("id", "name") values (1, '|) +insert into instruments ("id", "name") values (1, 'my_bass'); | + + + + +***Case 3:*** + +insert into instruments (|, name) values ('my_bass'); + +Results: +id - public.instruments.id (Column) +z - public.instruments.z (Column) +name - public.instruments.name (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments (i|, name) values ('my_bass'); +insert into instruments (id, |, name) values ('my_bass'); +**`name` is already written, so z should be suggested.** + +Results: +z - public.instruments.z (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +a - public.others.a (Column) +b - public.others.b (Column) + +-------------- + +insert into instruments (id, z |, name) values ('my_bass'); diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_where_clause.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_where_clause.snap new file mode 100644 index 000000000..40c42e8c9 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_where_clause.snap @@ -0,0 +1,109 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create table instruments ( + id bigint primary key generated always as identity, + name text not null, + z text, + created_at timestamp with time zone default now() +); + +create table others ( + a text, + b text, + c text +); + + +-------------- + +select name from instruments i join others o on i.z = o.a | +select name from instruments i join others o on i.z = o.a w| + +Results: +wait_event - pg_catalog.pg_stat_activity.wait_event (Column) +wait_event_type - pg_catalog.pg_stat_activity.wait_event_type (Column) +waitstart - pg_catalog.pg_locks.waitstart (Column) +wal_buffers_full - pg_catalog.pg_stat_wal.wal_buffers_full (Column) +wal_bytes - pg_catalog.pg_stat_wal.wal_bytes (Column) + +-------------- + +select name from instruments i join others o on i.z = o.a where | + +Results: +o.a - public.others.a (Column) +o.b - public.others.b (Column) +o.c - public.others.c (Column) +i.created_at - public.instruments.created_at (Column) +i.id - public.instruments.id (Column) + +-------------- + +select name from instruments i join others o on i.z = o.a where o.| +**should respect alias speciifcation** + +Results: +a - public.others.a (Column) +b - public.others.b (Column) +c - public.others.c (Column) + +-------------- + +select name from instruments i join others o on i.z = o.a where o.a | +select name from instruments i join others o on i.z = o.a where o.a = | +**should not prioritize suggest columns or schemas (right side of binary expression)** + +Results: +instruments - public.instruments (Table) +others - public.others (Table) +pg_catalog.RI_FKey_cascade_del() - Schema: pg_catalog.RI_FKey_cascade_del (Function) +pg_catalog.RI_FKey_cascade_upd() - Schema: pg_catalog.RI_FKey_cascade_upd (Function) +pg_catalog.RI_FKey_check_ins() - Schema: pg_catalog.RI_FKey_check_ins (Function) + +-------------- + +select name from instruments i join others o on i.z = o.a where o.a = i.| +select name from instruments i join others o on i.z = o.a where o.a = i.z | +select name from instruments i join others o on i.z = o.a where o.a = i.z a| + +Results: +abbrev - Schema: pg_catalog.abbrev (Function) +abs - Schema: pg_catalog.abs (Function) +aclcontains - Schema: pg_catalog.aclcontains (Function) +acldefault - Schema: pg_catalog.acldefault (Function) +aclexplode - Schema: pg_catalog.aclexplode (Function) + +-------------- + +select name from instruments i join others o on i.z = o.a where o.a = i.z and | +**should prioritize columns that aren't already mentioned** + +Results: +o.b - public.others.b (Column) +o.c - public.others.c (Column) +i.created_at - public.instruments.created_at (Column) +i.id - public.instruments.id (Column) +i.name - public.instruments.name (Column) + +-------------- + +select name from instruments i join others o on i.z = o.a where o.a = i.z and i.| +select name from instruments i join others o on i.z = o.a where o.a = i.z and i.i| +select name from instruments i join others o on i.z = o.a where o.a = i.z and i.id | +select name from instruments i join others o on i.z = o.a where o.a = i.z and i.id > | + +Results: +instruments - public.instruments (Table) +others - public.others (Table) +pg_catalog.RI_FKey_cascade_del() - Schema: pg_catalog.RI_FKey_cascade_del (Function) +pg_catalog.RI_FKey_cascade_upd() - Schema: pg_catalog.RI_FKey_cascade_upd (Function) +pg_catalog.RI_FKey_check_ins() - Schema: pg_catalog.RI_FKey_check_ins (Function) + +-------------- + +select name from instruments i join others o on i.z = o.a where o.a = i.z and i.id > 5| +select name from instruments i join others o on i.z = o.a where o.a = i.z and i.id > 5; | diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_policy_using_clause.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_policy_using_clause.snap new file mode 100644 index 000000000..d3db86856 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_policy_using_clause.snap @@ -0,0 +1,148 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create table instruments ( + id bigint primary key generated always as identity, + name text not null, + z text, + created_at timestamp with time zone default now() +); + + +-------------- + +***Case 1:*** + +create policy "my_pol" on public.instruments for select using (|) + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +create policy "my_pol" on public.instruments for select using (i|) + +Results: +id - public.instruments.id (Column) +ident - pg_catalog.pg_backend_memory_contexts.ident (Column) +identity_cycle - information_schema.columns.identity_cycle (Column) +identity_generation - information_schema.columns.identity_generation (Column) +identity_increment - information_schema.columns.identity_increment (Column) + +-------------- + +create policy "my_pol" on public.instruments for select using (id |) +create policy "my_pol" on public.instruments for select using (id = |) + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +create policy "my_pol" on public.instruments for select using (id = 1 |) +create policy "my_pol" on public.instruments for select using (id = 1 a|) +create policy "my_pol" on public.instruments for select using (id = 1 and |) + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +create policy "my_pol" on public.instruments for select using (id = 1 and c|) + +Results: +created_at - public.instruments.created_at (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +create policy "my_pol" on public.instruments for select using (id = 1 and created_at |) +create policy "my_pol" on public.instruments for select using (id = 1 and created_at > |) +create policy "my_pol" on public.instruments for select using (id = 1 and created_at > '|) +create policy "my_pol" on public.instruments for select using (id = 1 and created_at > '2025-01-01' |) + + + + +***Case 2:*** + +create policy "my_pol" on public.instruments for insert with check (|) + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +create policy "my_pol" on public.instruments for insert with check (i|) + +Results: +id - public.instruments.id (Column) +ident - pg_catalog.pg_backend_memory_contexts.ident (Column) +identity_cycle - information_schema.columns.identity_cycle (Column) +identity_generation - information_schema.columns.identity_generation (Column) +identity_increment - information_schema.columns.identity_increment (Column) + +-------------- + +create policy "my_pol" on public.instruments for insert with check (id |) +create policy "my_pol" on public.instruments for insert with check (id = |) + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +create policy "my_pol" on public.instruments for insert with check (id = 1 |) +create policy "my_pol" on public.instruments for insert with check (id = 1 a|) +create policy "my_pol" on public.instruments for insert with check (id = 1 and |) + +Results: +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +abbrev - pg_catalog.pg_timezone_abbrevs.abbrev (Column) + +-------------- + +create policy "my_pol" on public.instruments for insert with check (id = 1 and c|) + +Results: +created_at - public.instruments.created_at (Column) +cache_size - pg_catalog.pg_sequences.cache_size (Column) +calls - pg_catalog.pg_stat_user_functions.calls (Column) +castcontext - pg_catalog.pg_cast.castcontext (Column) +castfunc - pg_catalog.pg_cast.castfunc (Column) + +-------------- + +create policy "my_pol" on public.instruments for insert with check (id = 1 and created_at |) +create policy "my_pol" on public.instruments for insert with check (id = 1 and created_at > |) +create policy "my_pol" on public.instruments for insert with check (id = 1 and created_at > '|) +create policy "my_pol" on public.instruments for insert with check (id = 1 and created_at > '2025-01-01' |) diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_relevant_columns_without_letters.snap b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_relevant_columns_without_letters.snap new file mode 100644 index 000000000..2b903c41f --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_relevant_columns_without_letters.snap @@ -0,0 +1,64 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***Setup*** + +create table users ( + id serial primary key, + name text, + address text, + email text +); + + +-------------- + +s| +select | + +Results: +address - public.users.address (Column) +email - public.users.email (Column) +id - public.users.id (Column) +name - public.users.name (Column) +pg_catalog.RI_FKey_cascade_del() - Schema: pg_catalog.RI_FKey_cascade_del (Function) + +-------------- + +select n| + +Results: +name - public.users.name (Column) +nameconcatoid - Schema: pg_catalog.nameconcatoid (Function) +nameeq - Schema: pg_catalog.nameeq (Function) +nameeqtext - Schema: pg_catalog.nameeqtext (Function) +namege - Schema: pg_catalog.namege (Function) + +-------------- + +select name | +select name f| +select name from | + +Results: +public - public (Schema) +users - public.users (Table) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) +pg_toast - pg_toast (Schema) + +-------------- + +select name from u| + +Results: +public - public (Schema) +users - public.users (Table) +information_schema.udt_privileges - information_schema.udt_privileges (Table) +information_schema.usage_privileges - information_schema.usage_privileges (Table) +information_schema.user_defined_types - information_schema.user_defined_types (Table) + +-------------- + +select name from users | diff --git a/crates/pgls_completions/src/test_helper.rs b/crates/pgls_completions/src/test_helper.rs index cb27353a5..51769c652 100644 --- a/crates/pgls_completions/src/test_helper.rs +++ b/crates/pgls_completions/src/test_helper.rs @@ -1,7 +1,11 @@ +use insta::assert_snapshot; use pgls_schema_cache::SchemaCache; use pgls_test_utils::QueryWithCursorPosition; use pgls_text_size::TextRange; +use regex::Regex; use sqlx::{Executor, PgPool}; +use std::{collections::HashMap, fmt::Write, sync::OnceLock}; +use unindent::unindent; use crate::{CompletionItem, CompletionItemKind, CompletionParams, complete}; @@ -75,8 +79,10 @@ pub(crate) fn get_test_params<'a>( pub(crate) enum CompletionAssertion { Label(String), LabelAndKind(String, CompletionItemKind), + #[allow(unused)] LabelAndDesc(String, String), LabelNotExists(String), + #[allow(unused)] KindNotExists(CompletionItemKind), CompletionTextAndRange(String, TextRange), } @@ -199,5 +205,443 @@ pub(crate) async fn assert_no_complete_results(query: &str, setup: Option<&str>, let params = get_test_params(&tree, &cache, query.into()); let items = complete(params); + println!("Items: {:#?}", &items[..8]); + assert_eq!(items.len(), 0) } + +enum ChunkToType { + WithCompletions(String), + #[allow(unused)] + WithoutCompletions(String), +} + +static COMMENT_RE: OnceLock = OnceLock::new(); + +fn comment_regex() -> &'static Regex { + COMMENT_RE.get_or_init(|| Regex::new(r"<\d+>").unwrap()) +} + +pub(crate) struct TestCompletionsCase { + tokens_to_type: Vec, + surrounding_statement: String, + comments: std::collections::HashMap, + comment_position: usize, +} + +impl TestCompletionsCase { + pub(crate) fn new() -> Self { + Self { + tokens_to_type: Vec::new(), + surrounding_statement: String::new(), + comments: HashMap::new(), + comment_position: 0, + } + } + + pub(crate) fn inside_static_statement(mut self, it: &str) -> Self { + assert!(it.contains("")); + self.surrounding_statement = unindent(it); + self + } + + pub(crate) fn type_sql(mut self, it: &str) -> Self { + self.tokens_to_type + .push(ChunkToType::WithCompletions(it.trim().to_string())); + self + } + + #[allow(unused)] + pub(crate) fn type_without_completions(mut self, it: &str) -> Self { + self.tokens_to_type + .push(ChunkToType::WithoutCompletions(it.trim().to_string())); + self + } + + pub(crate) fn comment(mut self, comment: &str) -> Self { + self.comment_position += 1; + self.comments + .insert(self.comment_position, comment.to_string()); + self + } + + async fn generate_snapshot( + &self, + schema_cache: &SchemaCache, + parser: &mut tree_sitter::Parser, + ) -> String { + let mut stmt_parts = self.surrounding_statement.split(""); + let mut pre_sql = stmt_parts.next().unwrap().to_string(); + let post_sql = stmt_parts.next().unwrap_or("").to_string(); + + let mut snapshot_result = String::new(); + + for chunk in &self.tokens_to_type { + match chunk { + ChunkToType::WithCompletions(sql) => { + let whitespace_count = sql.chars().filter(|c| c.is_ascii_whitespace()).count(); + let whitespace_split = sql.split_ascii_whitespace().enumerate(); + + let mut should_close_with_paren = false; + + for (whitespace_idx, token) in whitespace_split { + let dot_count = token.chars().filter(|c| *c == '.').count(); + let dotted_split = token.split(".").enumerate(); + + for (dot_idx, og_part) in dotted_split { + let comment_indicator = comment_regex().find(og_part); + let comment = comment_indicator.and_then(|n| { + let num = n.as_str().replace("<", "").replace(">", ""); + let num: usize = num + .parse() + .expect("Regex should only find matches with numbers"); + self.comments.get(&num).map(|s| s.as_str()) + }); + + let part_without_comments = comment_regex().replace_all(og_part, ""); + + let starts_with_paren = part_without_comments.starts_with('('); + let ends_with_paren = part_without_comments.ends_with(')'); + + let part_without_parens = if starts_with_paren || ends_with_paren { + // we only want to sanitize when the token either starts or ends; that helps + // catch end tokens like `('something');` + part_without_comments.replace(['(', ')'], "") + } else { + part_without_comments.to_string() + }; + + let is_inside_quotes = part_without_parens.starts_with('"') + && part_without_parens.ends_with('"'); + + let part_without_quotes = part_without_parens.replace('"', ""); + + if !pre_sql.is_empty() { + let query = format!( + "{}{}{}{}{}", + pre_sql, + if dot_idx <= dot_count { "" } else { "." }, + QueryWithCursorPosition::cursor_marker(), + if should_close_with_paren { ")" } else { "" }, + post_sql, + ); + + self.completions_snapshot( + query.into(), + &mut snapshot_result, + schema_cache, + parser, + comment, + ) + .await; + } + + // try ` (|` and ` (|)` + if starts_with_paren { + let query1 = format!( + "{}{}({}{}", + pre_sql, + if dot_idx <= dot_count { "" } else { "." }, + QueryWithCursorPosition::cursor_marker(), + post_sql, + ); + + self.completions_snapshot( + query1.into(), + &mut snapshot_result, + schema_cache, + parser, + comment, + ) + .await; + + let query2 = format!( + "{}{}({}){}", + pre_sql, + if dot_idx <= dot_count { "" } else { "." }, + QueryWithCursorPosition::cursor_marker(), + post_sql, + ); + + self.completions_snapshot( + query2.into(), + &mut snapshot_result, + schema_cache, + parser, + comment, + ) + .await; + + pre_sql.push('('); + should_close_with_paren = true; + } + + // try ` "|` and ` "|"` + if is_inside_quotes { + let query1 = format!( + "{}{}\"{}{}{}", + pre_sql, + if dot_idx <= dot_count { "" } else { "." }, + QueryWithCursorPosition::cursor_marker(), + if should_close_with_paren { ")" } else { "" }, + post_sql, + ); + + self.completions_snapshot( + query1.into(), + &mut snapshot_result, + schema_cache, + parser, + comment, + ) + .await; + + let query2 = format!( + "{}{}\"{}\"{}{}", + pre_sql, + if dot_idx <= dot_count { "" } else { "." }, + QueryWithCursorPosition::cursor_marker(), + if should_close_with_paren { ")" } else { "" }, + post_sql, + ); + + self.completions_snapshot( + query2.into(), + &mut snapshot_result, + schema_cache, + parser, + comment, + ) + .await; + } + + if part_without_quotes.len() > 1 { + let first_token = &part_without_quotes[..1]; + + let query = format!( + "{}{}{}{}{}{}", + pre_sql, + if is_inside_quotes { + format!(r#""{first_token}"#) + } else { + first_token.to_string() + }, + QueryWithCursorPosition::cursor_marker(), + if is_inside_quotes { r#"""# } else { "" }, + if should_close_with_paren { ")" } else { "" }, + post_sql, + ); + + self.completions_snapshot( + query.into(), + &mut snapshot_result, + schema_cache, + parser, + if comment_indicator + .is_some_and(|txt| og_part.starts_with(txt.as_str())) + { + None + } else { + comment + }, + ) + .await; + }; + + if whitespace_idx == whitespace_count && dot_idx == dot_count { + let query = format!( + "{}{} {}{}", + pre_sql, + if is_inside_quotes { + format!(r#""{}""#, part_without_quotes.as_str()) + } else { + part_without_quotes.clone() + }, + QueryWithCursorPosition::cursor_marker(), + post_sql, + ); + + self.completions_snapshot( + query.into(), + &mut snapshot_result, + schema_cache, + parser, + None, + ) + .await; + } + + pre_sql.push_str(&part_without_parens); + + if dot_idx < dot_count { + pre_sql.push('.'); + } + + if ends_with_paren { + should_close_with_paren = false; + pre_sql.push(')'); + } + } + + if whitespace_idx < whitespace_count { + // note: we're sanitizing the white_space of typed SQL to simple spaces. + pre_sql.push(' '); + } + } + + pre_sql.push('\n'); + } + + ChunkToType::WithoutCompletions(sql) => { + pre_sql.push_str(sql.as_str()); + pre_sql.push('\n'); + } + } + } + + snapshot_result + } + + async fn completions_snapshot( + &self, + query: QueryWithCursorPosition, + writer: &mut String, + schema: &SchemaCache, + parser: &mut tree_sitter::Parser, + comment: Option<&str>, + ) { + let (pos, mut sql) = query.get_text_and_position(); + + let tree = parser.parse(&sql, None).expect("Invalid TS Tree!"); + + let params = CompletionParams { + text: sql.clone(), + position: (pos as u32).into(), + schema, + tree: &tree, + }; + + let items = complete(params); + + if pos < sql.len() { + sql.replace_range(pos..pos, "|"); + } else { + let diff = pos - sql.len(); + + sql.push_str(&" ".repeat(diff)); + sql.push('|'); + } + writeln!(writer, "{sql}").unwrap(); + + if let Some(c) = comment { + writeln!(writer, "**{c}**").unwrap(); + } + + if !items.is_empty() { + writeln!(writer).unwrap(); + writeln!(writer, "Results:").unwrap(); + + let max_idx = std::cmp::min(items.len(), 5); + for item in &items[..max_idx] { + write!( + writer, + "{}", + item.completion_text + .as_ref() + .filter(|c| !c.is_snippet) + .map(|c| c.text.as_str()) + .unwrap_or(item.label.as_str()) + ) + .unwrap(); + + write!(writer, " - ").unwrap(); + + match item.kind { + CompletionItemKind::Schema | CompletionItemKind::Role => {} + _ => { + write!(writer, "{}.", item.description).unwrap(); + } + } + + write!(writer, "{} ({})", item.label, item.kind).unwrap(); + + writeln!(writer).unwrap(); + } + + writeln!(writer).unwrap(); + + writeln!(writer, "--------------").unwrap(); + writeln!(writer).unwrap(); + } + } +} + +pub(crate) struct TestCompletionsSuite<'a> { + pool: &'a PgPool, + setup: Option<&'a str>, + cases: Vec, +} + +impl<'a> TestCompletionsSuite<'a> { + pub(crate) fn new(pool: &'a PgPool, setup: Option<&'a str>) -> Self { + Self { + pool, + setup, + cases: vec![], + } + } + + pub(crate) fn with_case(mut self, case: TestCompletionsCase) -> Self { + self.cases.push(case); + self + } + + pub(crate) async fn snapshot(self, snapshot_name: &str) { + assert!(!self.cases.is_empty(), "Needs at least one Snapshot case."); + + let mut final_snapshot = String::new(); + + if let Some(setup) = self.setup { + self.pool.execute(setup).await.expect("Problem with Setup"); + writeln!(final_snapshot, "***Setup***").unwrap(); + writeln!(final_snapshot).unwrap(); + write!(final_snapshot, "{}", unindent(setup)).unwrap(); + writeln!(final_snapshot).unwrap(); + writeln!(final_snapshot).unwrap(); + writeln!(final_snapshot, "--------------").unwrap(); + writeln!(final_snapshot).unwrap(); + } + + let cache = SchemaCache::load(self.pool) + .await + .expect("Problem loading SchemaCache"); + + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(&pgls_treesitter_grammar::LANGUAGE.into()) + .expect("Problem with TreeSitter Grammar"); + + let has_more_than_one_case = self.cases.len() > 1; + + for (idx, additional) in self.cases.iter().enumerate() { + if idx > 0 { + writeln!(final_snapshot).unwrap(); + writeln!(final_snapshot).unwrap(); + writeln!(final_snapshot).unwrap(); + + writeln!(final_snapshot).unwrap(); + } + + if has_more_than_one_case { + writeln!(final_snapshot, "***Case {}:***", idx + 1).unwrap(); + writeln!(final_snapshot).unwrap(); + } + + let snap = additional.generate_snapshot(&cache, &mut parser).await; + + write!(final_snapshot, "{snap}").unwrap(); + } + + assert_snapshot!(snapshot_name, final_snapshot) + } +} diff --git a/crates/pgls_hover/src/lib.rs b/crates/pgls_hover/src/lib.rs index 6276f7ba7..dbfdac68d 100644 --- a/crates/pgls_hover/src/lib.rs +++ b/crates/pgls_hover/src/lib.rs @@ -83,14 +83,15 @@ pub fn on_hover(params: OnHoverParams) -> Vec { _ => vec![], }, - HoveredNode::Function(node_identification) => match node_identification { - (maybe_schema, function_name) => params + HoveredNode::Function(node_identification) => { + let (maybe_schema, function_name) = node_identification; + params .schema_cache .find_functions(&function_name, maybe_schema.as_deref()) .into_iter() .map(Hoverable::from) - .collect(), - }, + .collect() + } HoveredNode::Role(role_name) => params .schema_cache diff --git a/crates/pgls_treesitter/src/context/mod.rs b/crates/pgls_treesitter/src/context/mod.rs index d61fac7fe..b6df990eb 100644 --- a/crates/pgls_treesitter/src/context/mod.rs +++ b/crates/pgls_treesitter/src/context/mod.rs @@ -153,6 +153,8 @@ impl<'a> TreesitterContext<'a> { ctx.gather_tree_context(); ctx.gather_info_from_ts_queries(); + println!("{} {:#?}", ctx.text, ctx.get_node_under_cursor_content(),); + ctx } diff --git a/todo.txt b/todo.txt new file mode 100644 index 000000000..d97e85ea5 --- /dev/null +++ b/todo.txt @@ -0,0 +1,22 @@ + +#### Snapshot review +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_in_join_on_clause.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns_with_aliases.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__does_not_complete_cols_in_join_clauses.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_join_on.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_select.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__handles_nested_queries.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__ignores_cols_in_from_clause.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_columns_of_mentioned_tables.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_not_mentioned_columns.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__shows_multiple_columns_if_no_relation_specified.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_alter_table_and_drop_table.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_insert_clause.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_where_clause.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_policy_using_clause.snap +- [ ] crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_relevant_columns_without_letters.snap + +#### General Todos +- [ ] Should filter out any items where typed letters don't match at all +- [ ]