From 5707bb88e7af3fcba3d5bdd3002cb3d4802934d7 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 7 Nov 2025 18:51:38 +0100 Subject: [PATCH 1/9] pickin up fast --- Cargo.lock | 18 +- crates/pgls_completions/Cargo.toml | 3 + .../pgls_completions/src/providers/columns.rs | 106 +++---- .../src/relevance/filtering.rs | 2 +- ...ompletions__test_helper__snapshot.snap.new | 90 ++++++ crates/pgls_completions/src/test_helper.rs | 294 ++++++++++++++++++ 6 files changed, 440 insertions(+), 73 deletions(-) create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__snapshot.snap.new diff --git a/Cargo.lock b/Cargo.lock index f3f5914ea..9df934efd 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", @@ -3634,13 +3636,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", ] @@ -3655,9 +3657,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", diff --git a/crates/pgls_completions/Cargo.toml b/crates/pgls_completions/Cargo.toml index cfb4fa864..7f89ff737 100644 --- a/crates/pgls_completions/Cargo.toml +++ b/crates/pgls_completions/Cargo.toml @@ -33,6 +33,9 @@ tokio = { version = "1.41.1", features = ["full"] } [dev-dependencies] criterion.workspace = true pgls_test_utils.workspace = true +insta.workspace = true +regex = "1.12.2" + [lib] doctest = false diff --git a/crates/pgls_completions/src/providers/columns.rs b/crates/pgls_completions/src/providers/columns.rs index a902579e4..764cd4780 100644 --- a/crates/pgls_completions/src/providers/columns.rs +++ b/crates/pgls_completions/src/providers/columns.rs @@ -58,7 +58,8 @@ mod tests { use crate::{ CompletionItem, CompletionItemKind, complete, test_helper::{ - CompletionAssertion, assert_complete_results, get_test_deps, get_test_params, + CompletionAssertion, TestCompletionsBuilder, assert_complete_results, get_test_deps, + get_test_params, }, }; @@ -381,7 +382,7 @@ mod tests { } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] - async fn filters_out_by_aliases(pool: PgPool) { + async fn filters_out_by_aliases_in_join_on(pool: PgPool) { let setup = r#" create schema auth; @@ -400,52 +401,41 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); + TestCompletionsBuilder::new(&pool, Some(setup)) + .prefix_static("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() + .await; + } - // 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; + #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] + async fn filters_out_by_aliases_in_select(pool: PgPool) { + let setup = r#" + create schema auth; - // 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() - ) - .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; + 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() + ); + "#; + + TestCompletionsBuilder::new(&pool, Some(setup)) + .type_sql("select u.id, p.pid<1>") + .comment("We should only get columns from the auth.posts table.") + .append_static("from auth.users u join auth.posts p on u.id = p.user_id;") + .snapshot() + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -468,24 +458,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; + TestCompletionsBuilder::new(&pool, Some(setup)) + .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() + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] diff --git a/crates/pgls_completions/src/relevance/filtering.rs b/crates/pgls_completions/src/relevance/filtering.rs index 7aa3531a6..c0c3087d6 100644 --- a/crates/pgls_completions/src/relevance/filtering.rs +++ b/crates/pgls_completions/src/relevance/filtering.rs @@ -324,7 +324,7 @@ impl CompletionFilter<'_> { WrappingClause::DropRole | WrappingClause::AlterRole => true, WrappingClause::SetStatement => ctx - .before_cursor_matches_kind(&["keyword_role", "keyword_authorization"]), + .before_cursor_matches_kind(&["eyword_role", "keyword_authorization"]), WrappingClause::RevokeStatement | WrappingClause::GrantStatement => { ctx.history_ends_with(&["role_specification", "any_identifier"]) diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__snapshot.snap.new b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__snapshot.snap.new new file mode 100644 index 000000000..4cd0170d9 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__snapshot.snap.new @@ -0,0 +1,90 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +assertion_line: 415 +expression: snapshot_result +--- +s| +from auth.users u join auth.posts p on u.id = p.user_id; + +No Results + +-------------- + +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) +name - auth.users.name (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) +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.pid | +from auth.users u join auth.posts p on u.id = p.user_id; + +No Results + +-------------- diff --git a/crates/pgls_completions/src/test_helper.rs b/crates/pgls_completions/src/test_helper.rs index cb27353a5..293b6d141 100644 --- a/crates/pgls_completions/src/test_helper.rs +++ b/crates/pgls_completions/src/test_helper.rs @@ -1,7 +1,9 @@ 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 crate::{CompletionItem, CompletionItemKind, CompletionParams, complete}; @@ -201,3 +203,295 @@ pub(crate) async fn assert_no_complete_results(query: &str, setup: Option<&str>, assert_eq!(items.len(), 0) } + +enum ChunkToType { + WithCompletions(String), + 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 TestCompletionsBuilder<'a> { + prefixes: Vec, + tokens_to_type: Vec, + appendices: Vec, + comments: std::collections::HashMap, + pool: &'a PgPool, + setup: Option<&'a str>, + comment_position: usize, +} + +impl<'a> TestCompletionsBuilder<'a> { + pub(crate) fn new(pool: &'a PgPool, setup: Option<&'a str>) -> Self { + Self { + prefixes: Vec::new(), + tokens_to_type: Vec::new(), + appendices: Vec::new(), + comments: HashMap::new(), + pool, + setup, + comment_position: 0, + } + } + + pub(crate) fn prefix_static(mut self, it: &str) -> Self { + self.prefixes.push(it.trim().to_string()); + self + } + + pub(crate) fn append_static(mut self, it: &str) -> Self { + self.appendices.push(it.trim().to_string()); + self + } + + pub(crate) fn type_sql(mut self, it: &str) -> Self { + assert_eq!( + self.appendices.len(), + 0, + "Make sure to call appendices LAST." + ); + self.tokens_to_type + .push(ChunkToType::WithCompletions(it.trim().to_string())); + self + } + + pub(crate) fn type_without_completions(mut self, it: &str) -> Self { + assert_eq!( + self.appendices.len(), + 0, + "Make sure to call appendices LAST." + ); + 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 + } + + pub(crate) async fn snapshot(self) { + if let Some(setup) = self.setup { + self.pool.execute(setup).await.expect("Invalid Setup!"); + } + + let schema_cache = SchemaCache::load(self.pool) + .await + .expect("Failed to load Schema Cache"); + + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(&pgls_treesitter_grammar::LANGUAGE.into()) + .expect("Error loading sql language"); + + let mut joined_prefix = self.prefixes.join("\n"); + if joined_prefix.len() > 0 { + joined_prefix.push_str("\n"); + }; + + let mut joined_appendix = String::new(); + if self.appendices.len() > 0 { + joined_appendix.push_str("\n"); + } + joined_appendix.push_str(self.appendices.join("\n").as_str()); + + 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(); + + 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 = comment_regex().replace_all(og_part, ""); + + if joined_prefix.len() > 0 { + let query = format!( + "{}{}{}{}", + joined_prefix, + if dot_idx <= dot_count { "" } else { "." }, + QueryWithCursorPosition::cursor_marker(), + joined_appendix, + ); + + self.completions_snapshot( + query.into(), + &mut snapshot_result, + &schema_cache, + &mut parser, + comment, + ) + .await; + } + + if part.len() > 1 { + let query = format!( + "{}{}{}{}", + joined_prefix, + &part[..1], + QueryWithCursorPosition::cursor_marker(), + joined_appendix, + ); + + self.completions_snapshot( + query.into(), + &mut snapshot_result, + &schema_cache, + &mut 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!( + "{}{} {}{}", + joined_prefix, + part, + QueryWithCursorPosition::cursor_marker(), + joined_appendix, + ); + + self.completions_snapshot( + query.into(), + &mut snapshot_result, + &schema_cache, + &mut parser, + None, + ) + .await; + } + + joined_prefix.push_str(&part); + + if dot_idx < dot_count { + joined_prefix.push_str("."); + } + } + + if whitespace_idx < whitespace_count { + // note: we're sanitizing the white_space of typed SQL to simple spaces. + joined_prefix.push_str(" "); + } + } + + joined_prefix.push_str("\n"); + } + + ChunkToType::WithoutCompletions(sql) => { + joined_prefix.push_str(sql.as_str()); + joined_prefix.push_str("\n"); + } + } + } + + insta::assert_snapshot!(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(); + if sql.len() == 0 { + return; + } + + println!("'{sql}', {pos}"); + + 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_str("|"); + } + writeln!(writer, "{sql}").unwrap(); + writeln!(writer).unwrap(); + + if let Some(c) = comment { + writeln!(writer, "**{}**", c).unwrap(); + writeln!(writer).unwrap(); + } + + if items.len() == 0 { + writeln!(writer, "No Results").unwrap(); + } else { + 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() + .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(); + } +} From 8b96f00bcabee82f9a8d3ca23e88dee6b4f9c866 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 8 Nov 2025 18:27:21 +0100 Subject: [PATCH 2/9] so far --- Cargo.lock | 10 + crates/pgls_completions/Cargo.toml | 1 + .../pgls_completions/src/providers/columns.rs | 1084 ++++------------- ...ompletions__test_helper__snapshot.snap.new | 90 -- crates/pgls_completions/src/test_helper.rs | 316 +++-- 5 files changed, 472 insertions(+), 1029 deletions(-) delete mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__snapshot.snap.new diff --git a/Cargo.lock b/Cargo.lock index 9df934efd..46c1a2869 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1999,6 +1999,15 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + [[package]] name = "insta" version = "1.42.1" @@ -2717,6 +2726,7 @@ dependencies = [ "async-std", "criterion", "fuzzy-matcher", + "indoc", "insta", "pgls_schema_cache", "pgls_test_utils", diff --git a/crates/pgls_completions/Cargo.toml b/crates/pgls_completions/Cargo.toml index 7f89ff737..32cc4cf20 100644 --- a/crates/pgls_completions/Cargo.toml +++ b/crates/pgls_completions/Cargo.toml @@ -35,6 +35,7 @@ criterion.workspace = true pgls_test_utils.workspace = true insta.workspace = true regex = "1.12.2" +indoc = "2.0.7" [lib] diff --git a/crates/pgls_completions/src/providers/columns.rs b/crates/pgls_completions/src/providers/columns.rs index 764cd4780..6b2b03692 100644 --- a/crates/pgls_completions/src/providers/columns.rs +++ b/crates/pgls_completions/src/providers/columns.rs @@ -50,16 +50,16 @@ fn get_completion_text(ctx: &TreesitterContext, col: &Column) -> CompletionText #[cfg(test)] mod tests { - use std::vec; + use indoc::indoc; use pgls_text_size::TextRange; use sqlx::{Executor, PgPool}; use crate::{ - CompletionItem, CompletionItemKind, complete, + CompletionItemKind, complete, test_helper::{ - CompletionAssertion, TestCompletionsBuilder, assert_complete_results, get_test_deps, - get_test_params, + CompletionAssertion, TestCompletionsCase, TestCompletionsSuite, + assert_complete_results, get_test_deps, get_test_params, }, }; @@ -67,9 +67,6 @@ mod tests { struct TestCase { query: String, - message: &'static str, - label: &'static str, - description: &'static str, } impl TestCase { @@ -80,7 +77,7 @@ mod tests { } #[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; @@ -100,62 +97,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(indoc! {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")] @@ -179,49 +134,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")] @@ -235,44 +155,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")] @@ -284,29 +170,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")] @@ -331,54 +208,27 @@ mod tests { ); "#; - pool.execute(setup).await.unwrap(); - - 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; - - 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; - - // 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; + 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; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -401,12 +251,17 @@ mod tests { ); "#; - TestCompletionsBuilder::new(&pool, Some(setup)) - .prefix_static("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() + 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; } @@ -430,11 +285,16 @@ mod tests { ); "#; - TestCompletionsBuilder::new(&pool, Some(setup)) - .type_sql("select u.id, p.pid<1>") - .comment("We should only get columns from the auth.posts table.") - .append_static("from auth.users u join auth.posts p on u.id = p.user_id;") - .snapshot() + 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."), + ) + .snapshot("filters_out_by_aliases_in_select") .await; } @@ -458,12 +318,12 @@ mod tests { ); "#; - TestCompletionsBuilder::new(&pool, Some(setup)) + 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() - .await; + ).snapshot("does_not_complete_cols_in_join_clauses").await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -486,41 +346,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")] @@ -543,76 +378,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")] @@ -631,69 +416,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() - ) - .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() + 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("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")] @@ -713,74 +454,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")] @@ -792,65 +478,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")] @@ -864,41 +527,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")] @@ -914,75 +559,12 @@ 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() + TestCompletionsSuite::new(&pool, Some(setup)) + .with_case( + TestCompletionsCase::new().type_sql(r#"select "email" from "private"."users";"#), ) - .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; + .snapshot("completes_quoted_columns") + .await; } #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] @@ -1003,240 +585,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, - ) + 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; - } - - { - // 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, - ) - .await; - } } } diff --git a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__snapshot.snap.new b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__snapshot.snap.new deleted file mode 100644 index 4cd0170d9..000000000 --- a/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__snapshot.snap.new +++ /dev/null @@ -1,90 +0,0 @@ ---- -source: crates/pgls_completions/src/test_helper.rs -assertion_line: 415 -expression: snapshot_result ---- -s| -from auth.users u join auth.posts p on u.id = p.user_id; - -No Results - --------------- - -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) -name - auth.users.name (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) -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.pid | -from auth.users u join auth.posts p on u.id = p.user_id; - -No Results - --------------- diff --git a/crates/pgls_completions/src/test_helper.rs b/crates/pgls_completions/src/test_helper.rs index 293b6d141..237076605 100644 --- a/crates/pgls_completions/src/test_helper.rs +++ b/crates/pgls_completions/src/test_helper.rs @@ -1,3 +1,4 @@ +use insta::assert_snapshot; use pgls_schema_cache::SchemaCache; use pgls_test_utils::QueryWithCursorPosition; use pgls_text_size::TextRange; @@ -77,8 +78,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), } @@ -206,6 +209,7 @@ pub(crate) async fn assert_no_complete_results(query: &str, setup: Option<&str>, enum ChunkToType { WithCompletions(String), + #[allow(unused)] WithoutCompletions(String), } @@ -215,56 +219,37 @@ fn comment_regex() -> &'static Regex { COMMENT_RE.get_or_init(|| Regex::new(r"<\d+>").unwrap()) } -pub(crate) struct TestCompletionsBuilder<'a> { - prefixes: Vec, +pub(crate) struct TestCompletionsCase { tokens_to_type: Vec, - appendices: Vec, + surrounding_statement: String, comments: std::collections::HashMap, - pool: &'a PgPool, - setup: Option<&'a str>, comment_position: usize, } -impl<'a> TestCompletionsBuilder<'a> { - pub(crate) fn new(pool: &'a PgPool, setup: Option<&'a str>) -> Self { +impl TestCompletionsCase { + pub(crate) fn new() -> Self { Self { - prefixes: Vec::new(), tokens_to_type: Vec::new(), - appendices: Vec::new(), + surrounding_statement: String::new(), comments: HashMap::new(), - pool, - setup, comment_position: 0, } } - pub(crate) fn prefix_static(mut self, it: &str) -> Self { - self.prefixes.push(it.trim().to_string()); - self - } - - pub(crate) fn append_static(mut self, it: &str) -> Self { - self.appendices.push(it.trim().to_string()); + pub(crate) fn inside_static_statement(mut self, it: &str) -> Self { + assert!(it.contains("")); + self.surrounding_statement = it.trim().to_string(); self } pub(crate) fn type_sql(mut self, it: &str) -> Self { - assert_eq!( - self.appendices.len(), - 0, - "Make sure to call appendices LAST." - ); 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 { - assert_eq!( - self.appendices.len(), - 0, - "Make sure to call appendices LAST." - ); self.tokens_to_type .push(ChunkToType::WithoutCompletions(it.trim().to_string())); self @@ -277,30 +262,14 @@ impl<'a> TestCompletionsBuilder<'a> { self } - pub(crate) async fn snapshot(self) { - if let Some(setup) = self.setup { - self.pool.execute(setup).await.expect("Invalid Setup!"); - } - - let schema_cache = SchemaCache::load(self.pool) - .await - .expect("Failed to load Schema Cache"); - - let mut parser = tree_sitter::Parser::new(); - parser - .set_language(&pgls_treesitter_grammar::LANGUAGE.into()) - .expect("Error loading sql language"); - - let mut joined_prefix = self.prefixes.join("\n"); - if joined_prefix.len() > 0 { - joined_prefix.push_str("\n"); - }; - - let mut joined_appendix = String::new(); - if self.appendices.len() > 0 { - joined_appendix.push_str("\n"); - } - joined_appendix.push_str(self.appendices.join("\n").as_str()); + 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(); @@ -310,6 +279,8 @@ impl<'a> TestCompletionsBuilder<'a> { 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(); @@ -324,41 +295,145 @@ impl<'a> TestCompletionsBuilder<'a> { self.comments.get(&num).map(|s| s.as_str()) }); - let part = comment_regex().replace_all(og_part, ""); + 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(')'); - if joined_prefix.len() > 0 { + 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('(', "").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.len() > 0 { let query = format!( - "{}{}{}{}", - joined_prefix, + "{}{}{}{}{}", + pre_sql, if dot_idx <= dot_count { "" } else { "." }, QueryWithCursorPosition::cursor_marker(), - joined_appendix, + if should_close_with_paren { ")" } else { "" }, + post_sql, ); self.completions_snapshot( query.into(), &mut snapshot_result, &schema_cache, - &mut parser, + 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_str("("); + 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.len() > 1 { + if part_without_quotes.len() > 1 { + let first_token = &part_without_quotes[..1]; + let query = format!( - "{}{}{}{}", - joined_prefix, - &part[..1], + "{}{}{}{}{}{}", + pre_sql, + if is_inside_quotes { + format!(r#""{first_token}"#) + } else { + first_token.to_string() + }, QueryWithCursorPosition::cursor_marker(), - joined_appendix, + if is_inside_quotes { r#"""# } else { "" }, + if should_close_with_paren { ")" } else { "" }, + post_sql, ); self.completions_snapshot( query.into(), &mut snapshot_result, &schema_cache, - &mut parser, + parser, if comment_indicator .is_some_and(|txt| og_part.starts_with(txt.as_str())) { @@ -373,46 +448,55 @@ impl<'a> TestCompletionsBuilder<'a> { if whitespace_idx == whitespace_count && dot_idx == dot_count { let query = format!( "{}{} {}{}", - joined_prefix, - part, + pre_sql, + if is_inside_quotes { + format!(r#""{}""#, part_without_quotes.as_str()) + } else { + part_without_quotes.clone() + }, QueryWithCursorPosition::cursor_marker(), - joined_appendix, + post_sql, ); self.completions_snapshot( query.into(), &mut snapshot_result, &schema_cache, - &mut parser, + parser, None, ) .await; } - joined_prefix.push_str(&part); + pre_sql.push_str(&part_without_parens); if dot_idx < dot_count { - joined_prefix.push_str("."); + pre_sql.push_str("."); + } + + if ends_with_paren { + should_close_with_paren = false; + pre_sql.push_str(")"); } } if whitespace_idx < whitespace_count { // note: we're sanitizing the white_space of typed SQL to simple spaces. - joined_prefix.push_str(" "); + pre_sql.push_str(" "); } } - joined_prefix.push_str("\n"); + pre_sql.push_str("\n"); } ChunkToType::WithoutCompletions(sql) => { - joined_prefix.push_str(sql.as_str()); - joined_prefix.push_str("\n"); + pre_sql.push_str(sql.as_str()); + pre_sql.push_str("\n"); } } } - insta::assert_snapshot!(snapshot_result); + snapshot_result } async fn completions_snapshot( @@ -424,11 +508,6 @@ impl<'a> TestCompletionsBuilder<'a> { comment: Option<&str>, ) { let (pos, mut sql) = query.get_text_and_position(); - if sql.len() == 0 { - return; - } - - println!("'{sql}', {pos}"); let tree = parser.parse(&sql, None).expect("Invalid TS Tree!"); @@ -450,16 +529,13 @@ impl<'a> TestCompletionsBuilder<'a> { sql.push_str("|"); } writeln!(writer, "{sql}").unwrap(); - writeln!(writer).unwrap(); if let Some(c) = comment { writeln!(writer, "**{}**", c).unwrap(); - writeln!(writer).unwrap(); } - if items.len() == 0 { - writeln!(writer, "No Results").unwrap(); - } else { + if !items.is_empty() { + writeln!(writer).unwrap(); writeln!(writer, "Results:").unwrap(); let max_idx = std::cmp::min(items.len(), 5); @@ -469,6 +545,7 @@ impl<'a> TestCompletionsBuilder<'a> { "{}", item.completion_text .as_ref() + .filter(|c| !c.is_snippet) .map(|c| c.text.as_str()) .unwrap_or(item.label.as_str()) ) @@ -487,11 +564,74 @@ impl<'a> TestCompletionsBuilder<'a> { writeln!(writer).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."); + + if let Some(setup) = self.setup { + self.pool.execute(setup).await.expect("Problem with Setup"); + } + + 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 mut final_snapshot = String::new(); + + 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(); + } - writeln!(writer, "--------------").unwrap(); - writeln!(writer).unwrap(); + assert_snapshot!(snapshot_name, final_snapshot) } } From 799f52d29fde4109c92c002e6a9393c5103db8f8 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 8 Nov 2025 18:29:56 +0100 Subject: [PATCH 3/9] changie --- .../pgls_completions/src/providers/columns.rs | 1 - ...t_helper__completes_in_join_on_clause.snap | 83 ++ ...test_helper__completes_quoted_columns.snap | 110 +++ ...completes_quoted_columns_with_aliases.snap | 462 +++++++++++ ...oes_not_complete_cols_in_join_clauses.snap | 202 +++++ ...er__filters_out_by_aliases_in_join_on.snap | 91 +++ ...per__filters_out_by_aliases_in_select.snap | 70 ++ ...__test_helper__handles_nested_queries.snap | 163 ++++ ...t_helper__ignores_cols_in_from_clause.snap | 43 + ...__prefers_columns_of_mentioned_tables.snap | 96 +++ ...helper__prefers_not_mentioned_columns.snap | 314 ++++++++ ...iple_columns_if_no_relation_specified.snap | 30 + ...columns_in_alter_table_and_drop_table.snap | 748 ++++++++++++++++++ ...er__suggests_columns_in_insert_clause.snap | 265 +++++++ ...per__suggests_columns_in_where_clause.snap | 131 +++ ..._suggests_columns_policy_using_clause.snap | 136 ++++ ...ests_relevant_columns_without_letters.snap | 52 ++ 17 files changed, 2996 insertions(+), 1 deletion(-) create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_in_join_on_clause.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns_with_aliases.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__does_not_complete_cols_in_join_clauses.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_join_on.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_select.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__handles_nested_queries.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__ignores_cols_in_from_clause.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_columns_of_mentioned_tables.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_not_mentioned_columns.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__shows_multiple_columns_if_no_relation_specified.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_alter_table_and_drop_table.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_insert_clause.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_where_clause.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_policy_using_clause.snap create mode 100644 crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_relevant_columns_without_letters.snap diff --git a/crates/pgls_completions/src/providers/columns.rs b/crates/pgls_completions/src/providers/columns.rs index 6b2b03692..2e70b14ba 100644 --- a/crates/pgls_completions/src/providers/columns.rs +++ b/crates/pgls_completions/src/providers/columns.rs @@ -52,7 +52,6 @@ fn get_completion_text(ctx: &TreesitterContext, col: &Column) -> CompletionText mod tests { use indoc::indoc; - use pgls_text_size::TextRange; use sqlx::{Executor, PgPool}; use crate::{ 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..cfcd0eefe --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_in_join_on_clause.snap @@ -0,0 +1,83 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +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) +pid - auth.posts.pid (Column) +content - auth.posts.content (Column) +created_at - auth.posts.created_at (Column) +title - auth.posts.title (Column) + +-------------- + +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 = | + +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) +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.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..c63e3fd40 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns.snap @@ -0,0 +1,110 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +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) +id - private.users.id (Column) +elem_count_histogram - pg_catalog.pg_stats.elem_count_histogram (Column) + +-------------- + +select "email" | +select "email" f| +select "email" from | + +Results: +public - public (Schema) +private - private (Schema) +private.users - private.users (Table) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) + +-------------- + +select "email" from "| + +Results: +public - public (Schema) +private - private (Schema) +private"."users" - private.users (Table) +information_schema - information_schema (Schema) +pg_catalog - pg_catalog (Schema) + +-------------- + +select "email" from "|" + +Results: +public - public (Schema) +private - private (Schema) +private"."users - private.users (Table) +information_schema"."_pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema"."_pg_foreign_servers - information_schema._pg_foreign_servers (Table) + +-------------- + +select "email" from "p|" + +Results: +public - public (Schema) +private - private (Schema) +private"."users - private.users (Table) +information_schema"."parameters - information_schema.parameters (Table) +pg_catalog"."pg_aggregate - pg_catalog.pg_aggregate (Table) + +-------------- + +select "email" from "private".| + +Results: +users - private.users (Table) + +-------------- + +select "email" from "private".u| + +Results: +users - private.users (Table) + +-------------- + +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..110c63141 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__completes_quoted_columns_with_aliases.snap @@ -0,0 +1,462 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***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: +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 "pr".| +select "pr"."| +select "pr"."|" +select "pr"."e|" +select "pr"."email" | +select "pr"."email" f| +select "pr"."email" from | + +Results: +public - public (Schema) +private - private (Schema) +names - public.names (Table) +private.users - private.users (Table) +information_schema - information_schema (Schema) + +-------------- + +select "pr"."email" from p| + +Results: +public - public (Schema) +private - private (Schema) +names - public.names (Table) +private.users - private.users (Table) +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) +pr"."id - private.users.id (Column) +name - public.names.name (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) +name - public.names.name (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) +id - private.users.id (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) +name - public.names.name (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) +id - private.users.id (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) +name - public.names.name (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) +id - private.users.id (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: +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 "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) +id - private.users.id (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) +name - public.names.name (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..47dad0792 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__does_not_complete_cols_in_join_clauses.snap @@ -0,0 +1,202 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +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) +public - public (Schema) +auth.posts - auth.posts (Table) +auth.users - auth.users (Table) +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) +posts - auth.posts (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) +public - public (Schema) +auth.posts - auth.posts (Table) +auth.users - auth.users (Table) +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) +users - auth.users (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) +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.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 = | + +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) +pid - auth.posts.pid (Column) +content - auth.posts.content (Column) +created_at - auth.posts.created_at (Column) +title - auth.posts.title (Column) + +-------------- + +select u.uid, p.content from auth.users u join auth.posts p on u.uid = 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) + +-------------- 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..3e7740ed4 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_join_on.snap @@ -0,0 +1,91 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +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) +name - auth.users.name (Column) + +-------------- + +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 = | + +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) +pid - auth.posts.pid (Column) +content - auth.posts.content (Column) +created_at - auth.posts.created_at (Column) +title - auth.posts.title (Column) + +-------------- + +select u.id, p.content from auth.users u join auth.posts p on u.id = 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) + +-------------- 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..fb3f34286 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__filters_out_by_aliases_in_select.snap @@ -0,0 +1,70 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +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) +name - auth.users.name (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) +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.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..34f80a211 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__handles_nested_queries.snap @@ -0,0 +1,163 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +select * from ( + | +) as subquery +join public.users u +on u.id = subquery.id; + +Results: +audio_books - public.audio_books (Table) +users - public.users (Table) +private.audio_books - private.audio_books (Table) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) + +-------------- + +select * from ( + s| +) as subquery +join public.users u +on u.id = subquery.id; + +Results: +audio_books - public.audio_books (Table) +users - public.users (Table) +private.audio_books - private.audio_books (Table) +information_schema._pg_foreign_data_wrappers - information_schema._pg_foreign_data_wrappers (Table) +information_schema._pg_foreign_servers - information_schema._pg_foreign_servers (Table) + +-------------- + +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) +audio_books - public.audio_books (Table) +users - public.users (Table) +private.audio_books - private.audio_books (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..132e2a417 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__ignores_cols_in_from_clause.snap @@ -0,0 +1,43 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +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) +private.users - private.users (Table) +information_schema.parameters - information_schema.parameters (Table) +pg_catalog.pg_aggregate - pg_catalog.pg_aggregate (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..9aee13885 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_columns_of_mentioned_tables.snap @@ -0,0 +1,96 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***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) +id2 - public.users.id2 (Column) +settings - public.users.settings (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) +id1 - private.users.id1 (Column) +user_settings - private.users.user_settings (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) +email1 - private.users.email1 (Column) +id1 - private.users.id1 (Column) +name1 - private.users.name1 (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..82b8ffa88 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__prefers_not_mentioned_columns.snap @@ -0,0 +1,314 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***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) +a - public.one.a (Column) +b - public.one.b (Column) +z - public.one.z (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) +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; +**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) +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: +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) +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: +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) +e - public.two.e (Column) +o.z - public.one.z (Column) +o.id - public.one.id (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) +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; +**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: +o.id - public.one.id (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, | 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) +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.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) +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: +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) +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: +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) +e - public.two.e (Column) +o.z - public.one.z (Column) +o.id - public.one.id (Column) +o.a - public.one.a (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) +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; +**`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..5ece5410f --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__shows_multiple_columns_if_no_relation_specified.snap @@ -0,0 +1,30 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +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) +id - public.audio_books.id (Column) +name - Schema: pg_catalog.name (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..3898dfc62 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_alter_table_and_drop_table.snap @@ -0,0 +1,748 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***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) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +cache_size - pg_catalog.pg_sequences.cache_size (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) +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_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) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +cache_size - pg_catalog.pg_sequences.cache_size (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) +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_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) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +cache_size - pg_catalog.pg_sequences.cache_size (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) +created_at - public.instruments.created_at (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +ident - pg_catalog.pg_backend_memory_contexts.ident (Column) + +-------------- + +alter table instruments drop column if | + +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 e| + +Results: +created_at - public.instruments.created_at (Column) +name - public.instruments.name (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +elem_count_histogram - pg_catalog.pg_stats.elem_count_histogram (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) +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_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) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +cache_size - pg_catalog.pg_sequences.cache_size (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) +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_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) +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_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) +instruments - public.instruments (Table) +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) + +-------------- + +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) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +cache_size - pg_catalog.pg_sequences.cache_size (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) +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_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) +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_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) +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_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) +instruments - public.instruments (Table) +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) + +-------------- + +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) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +cache_size - pg_catalog.pg_sequences.cache_size (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) +created_at - public.instruments.created_at (Column) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +n_dead_tup - pg_catalog.pg_stat_all_tables.n_dead_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..82fdec2f1 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_insert_clause.snap @@ -0,0 +1,265 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***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) +others - public.others (Table) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (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) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +id - public.others.id (Column) +a - public.others.a (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) +z - public.instruments.z (Column) +id - public.instruments.id (Column) +a - public.others.a (Column) +b - public.others.b (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) +others - public.others (Table) +information_schema - information_schema (Schema) +information_schema.information_schema_catalog_name - information_schema.information_schema_catalog_name (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) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +id - public.others.id (Column) +a - public.others.a (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) +id - public.instruments.id (Column) +z - public.instruments.z (Column) +a - public.others.a (Column) +b - public.others.b (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..601edcdeb --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_in_where_clause.snap @@ -0,0 +1,131 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +select name from instruments i join others o on i.z = o.a | + +Results: +i.id - public.instruments.id (Column) +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) + +-------------- + +select name from instruments i join others o on i.z = o.a w| + +Results: +i.id - public.instruments.id (Column) +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) + +-------------- + +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 | + +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.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 | + +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 a| + +Results: +instruments - public.instruments (Table) +others - public.others (Table) +abbrev - Schema: pg_catalog.abbrev (Function) +abs - Schema: pg_catalog.abs (Function) +aclcontains - Schema: pg_catalog.aclcontains (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 | + +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 > | + +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..8d1ef12a3 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_columns_policy_using_clause.snap @@ -0,0 +1,136 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +***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) +created_at - public.instruments.created_at (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +ident - pg_catalog.pg_backend_memory_contexts.ident (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) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +cache_size - pg_catalog.pg_sequences.cache_size (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) +created_at - public.instruments.created_at (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +ident - pg_catalog.pg_backend_memory_contexts.ident (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) +id - public.instruments.id (Column) +name - public.instruments.name (Column) +z - public.instruments.z (Column) +cache_size - pg_catalog.pg_sequences.cache_size (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..ca7421a06 --- /dev/null +++ b/crates/pgls_completions/src/snapshots/pgls_completions__test_helper__suggests_relevant_columns_without_letters.snap @@ -0,0 +1,52 @@ +--- +source: crates/pgls_completions/src/test_helper.rs +expression: final_snapshot +--- +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) +address - public.users.address (Column) +email - public.users.email (Column) +id - public.users.id (Column) +name - Schema: pg_catalog.name (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: +users - public.users (Table) +public - public (Schema) +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 | From f8ca4077065008c5d740542750d08943500a8ba6 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 8 Nov 2025 18:35:13 +0100 Subject: [PATCH 4/9] readied --- crates/pgls_completions/Cargo.toml | 6 ++-- .../pgls_completions/src/providers/columns.rs | 21 ++---------- crates/pgls_completions/src/test_helper.rs | 34 +++++++++---------- 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/crates/pgls_completions/Cargo.toml b/crates/pgls_completions/Cargo.toml index 32cc4cf20..73a5a7450 100644 --- a/crates/pgls_completions/Cargo.toml +++ b/crates/pgls_completions/Cargo.toml @@ -32,10 +32,10 @@ tokio = { version = "1.41.1", features = ["full"] } [dev-dependencies] criterion.workspace = true +indoc = "2.0.7" +insta.workspace = true pgls_test_utils.workspace = true -insta.workspace = true -regex = "1.12.2" -indoc = "2.0.7" +regex = "1.12.2" [lib] diff --git a/crates/pgls_completions/src/providers/columns.rs b/crates/pgls_completions/src/providers/columns.rs index 2e70b14ba..a951cf04e 100644 --- a/crates/pgls_completions/src/providers/columns.rs +++ b/crates/pgls_completions/src/providers/columns.rs @@ -52,29 +52,12 @@ fn get_completion_text(ctx: &TreesitterContext, col: &Column) -> CompletionText mod tests { use indoc::indoc; - use sqlx::{Executor, PgPool}; + use sqlx::PgPool; - use crate::{ - CompletionItemKind, complete, - test_helper::{ - CompletionAssertion, TestCompletionsCase, TestCompletionsSuite, - assert_complete_results, get_test_deps, get_test_params, - }, - }; + use crate::test_helper::{TestCompletionsCase, TestCompletionsSuite}; use pgls_test_utils::QueryWithCursorPosition; - struct TestCase { - query: String, - } - - impl TestCase { - fn get_input_query(&self) -> QueryWithCursorPosition { - let strs: Vec<&str> = self.query.split_whitespace().collect(); - strs.join(" ").as_str().into() - } - } - #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] async fn handles_nested_queries(pool: PgPool) { let setup = r#" diff --git a/crates/pgls_completions/src/test_helper.rs b/crates/pgls_completions/src/test_helper.rs index 237076605..a4d9fe201 100644 --- a/crates/pgls_completions/src/test_helper.rs +++ b/crates/pgls_completions/src/test_helper.rs @@ -303,7 +303,7 @@ impl TestCompletionsCase { 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('(', "").replace(')', "") + part_without_comments.replace(['(', ')'], "") } else { part_without_comments.to_string() }; @@ -313,7 +313,7 @@ impl TestCompletionsCase { let part_without_quotes = part_without_parens.replace('"', ""); - if pre_sql.len() > 0 { + if !pre_sql.is_empty() { let query = format!( "{}{}{}{}{}", pre_sql, @@ -326,7 +326,7 @@ impl TestCompletionsCase { self.completions_snapshot( query.into(), &mut snapshot_result, - &schema_cache, + schema_cache, parser, comment, ) @@ -346,7 +346,7 @@ impl TestCompletionsCase { self.completions_snapshot( query1.into(), &mut snapshot_result, - &schema_cache, + schema_cache, parser, comment, ) @@ -363,13 +363,13 @@ impl TestCompletionsCase { self.completions_snapshot( query2.into(), &mut snapshot_result, - &schema_cache, + schema_cache, parser, comment, ) .await; - pre_sql.push_str("("); + pre_sql.push('('); should_close_with_paren = true; } @@ -387,7 +387,7 @@ impl TestCompletionsCase { self.completions_snapshot( query1.into(), &mut snapshot_result, - &schema_cache, + schema_cache, parser, comment, ) @@ -405,7 +405,7 @@ impl TestCompletionsCase { self.completions_snapshot( query2.into(), &mut snapshot_result, - &schema_cache, + schema_cache, parser, comment, ) @@ -432,7 +432,7 @@ impl TestCompletionsCase { self.completions_snapshot( query.into(), &mut snapshot_result, - &schema_cache, + schema_cache, parser, if comment_indicator .is_some_and(|txt| og_part.starts_with(txt.as_str())) @@ -461,7 +461,7 @@ impl TestCompletionsCase { self.completions_snapshot( query.into(), &mut snapshot_result, - &schema_cache, + schema_cache, parser, None, ) @@ -471,27 +471,27 @@ impl TestCompletionsCase { pre_sql.push_str(&part_without_parens); if dot_idx < dot_count { - pre_sql.push_str("."); + pre_sql.push('.'); } if ends_with_paren { should_close_with_paren = false; - pre_sql.push_str(")"); + pre_sql.push(')'); } } if whitespace_idx < whitespace_count { // note: we're sanitizing the white_space of typed SQL to simple spaces. - pre_sql.push_str(" "); + pre_sql.push(' '); } } - pre_sql.push_str("\n"); + pre_sql.push('\n'); } ChunkToType::WithoutCompletions(sql) => { pre_sql.push_str(sql.as_str()); - pre_sql.push_str("\n"); + pre_sql.push('\n'); } } } @@ -526,12 +526,12 @@ impl TestCompletionsCase { let diff = pos - sql.len(); sql.push_str(&" ".repeat(diff)); - sql.push_str("|"); + sql.push('|'); } writeln!(writer, "{sql}").unwrap(); if let Some(c) = comment { - writeln!(writer, "**{}**", c).unwrap(); + writeln!(writer, "**{c}**").unwrap(); } if !items.is_empty() { From 6365f80940314713acaedd86724d105cb75acb97 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 8 Nov 2025 18:36:11 +0100 Subject: [PATCH 5/9] more removed --- crates/pgls_completions/src/providers/columns.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/pgls_completions/src/providers/columns.rs b/crates/pgls_completions/src/providers/columns.rs index a951cf04e..6ef99ebad 100644 --- a/crates/pgls_completions/src/providers/columns.rs +++ b/crates/pgls_completions/src/providers/columns.rs @@ -56,8 +56,6 @@ mod tests { use crate::test_helper::{TestCompletionsCase, TestCompletionsSuite}; - use pgls_test_utils::QueryWithCursorPosition; - #[sqlx::test(migrator = "pgls_test_utils::MIGRATIONS")] async fn handles_nested_queries(pool: PgPool) { let setup = r#" From 4324377d43f575e1057e514d329baf042685d5b6 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 8 Nov 2025 18:38:58 +0100 Subject: [PATCH 6/9] minor changes --- crates/pgls_diagnostics/src/serde.rs | 1 - crates/pgls_hover/src/lib.rs | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/pgls_diagnostics/src/serde.rs b/crates/pgls_diagnostics/src/serde.rs index ab57d90fa..babb5d93d 100644 --- a/crates/pgls_diagnostics/src/serde.rs +++ b/crates/pgls_diagnostics/src/serde.rs @@ -164,7 +164,6 @@ impl From> for Location { #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(test, derive(Eq, PartialEq))] - struct Advices { advices: Vec, } 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 From 9586d8cccc2b905dc514a5424ca9509c8368931a Mon Sep 17 00:00:00 2001 From: Julian Domke <68325451+juleswritescode@users.noreply.github.com> Date: Sat, 8 Nov 2025 18:40:58 +0100 Subject: [PATCH 7/9] Update crates/pgls_completions/src/relevance/filtering.rs --- crates/pgls_completions/src/relevance/filtering.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pgls_completions/src/relevance/filtering.rs b/crates/pgls_completions/src/relevance/filtering.rs index c0c3087d6..7aa3531a6 100644 --- a/crates/pgls_completions/src/relevance/filtering.rs +++ b/crates/pgls_completions/src/relevance/filtering.rs @@ -324,7 +324,7 @@ impl CompletionFilter<'_> { WrappingClause::DropRole | WrappingClause::AlterRole => true, WrappingClause::SetStatement => ctx - .before_cursor_matches_kind(&["eyword_role", "keyword_authorization"]), + .before_cursor_matches_kind(&["keyword_role", "keyword_authorization"]), WrappingClause::RevokeStatement | WrappingClause::GrantStatement => { ctx.history_ends_with(&["role_specification", "any_identifier"]) From c3201e7398920eefaf9a27b43038e8512770b835 Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 9 Nov 2025 09:24:01 +0100 Subject: [PATCH 8/9] use indoc, add setup --- Cargo.lock | 17 ++++++-------- crates/pgls_completions/Cargo.toml | 2 +- .../pgls_completions/src/providers/columns.rs | 6 ++--- ...t_helper__completes_in_join_on_clause.snap | 21 +++++++++++++++++ ...test_helper__completes_quoted_columns.snap | 14 +++++++++++ ...completes_quoted_columns_with_aliases.snap | 19 +++++++++++++++ ...oes_not_complete_cols_in_join_clauses.snap | 21 +++++++++++++++++ ...er__filters_out_by_aliases_in_join_on.snap | 21 +++++++++++++++++ ...per__filters_out_by_aliases_in_select.snap | 21 +++++++++++++++++ ...__test_helper__handles_nested_queries.snap | 22 ++++++++++++++++++ ...t_helper__ignores_cols_in_from_clause.snap | 15 ++++++++++++ ...__prefers_columns_of_mentioned_tables.snap | 23 +++++++++++++++++++ ...helper__prefers_not_mentioned_columns.snap | 21 +++++++++++++++++ ...iple_columns_if_no_relation_specified.snap | 22 ++++++++++++++++++ ...columns_in_alter_table_and_drop_table.snap | 12 ++++++++++ ...er__suggests_columns_in_insert_clause.snap | 17 ++++++++++++++ ...per__suggests_columns_in_where_clause.snap | 18 +++++++++++++++ ..._suggests_columns_policy_using_clause.snap | 12 ++++++++++ ...ests_relevant_columns_without_letters.snap | 12 ++++++++++ crates/pgls_completions/src/test_helper.rs | 14 ++++++++--- 20 files changed, 312 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46c1a2869..803c04887 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1999,15 +1999,6 @@ dependencies = [ "serde", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "insta" version = "1.42.1" @@ -2726,7 +2717,6 @@ dependencies = [ "async-std", "criterion", "fuzzy-matcher", - "indoc", "insta", "pgls_schema_cache", "pgls_test_utils", @@ -2741,6 +2731,7 @@ dependencies = [ "tokio", "tracing", "tree-sitter", + "unindent", ] [[package]] @@ -5037,6 +5028,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/crates/pgls_completions/Cargo.toml b/crates/pgls_completions/Cargo.toml index 73a5a7450..6061faf60 100644 --- a/crates/pgls_completions/Cargo.toml +++ b/crates/pgls_completions/Cargo.toml @@ -32,7 +32,7 @@ tokio = { version = "1.41.1", features = ["full"] } [dev-dependencies] criterion.workspace = true -indoc = "2.0.7" +unindent = "0.2.4" insta.workspace = true pgls_test_utils.workspace = true regex = "1.12.2" diff --git a/crates/pgls_completions/src/providers/columns.rs b/crates/pgls_completions/src/providers/columns.rs index 6ef99ebad..eda6fdcd1 100644 --- a/crates/pgls_completions/src/providers/columns.rs +++ b/crates/pgls_completions/src/providers/columns.rs @@ -50,8 +50,6 @@ fn get_completion_text(ctx: &TreesitterContext, col: &Column) -> CompletionText #[cfg(test)] mod tests { - use indoc::indoc; - use sqlx::PgPool; use crate::test_helper::{TestCompletionsCase, TestCompletionsSuite}; @@ -79,13 +77,13 @@ mod tests { TestCompletionsSuite::new(&pool, Some(setup)).with_case( TestCompletionsCase::new() - .inside_static_statement(indoc! {r#" + .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.") ) 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 index cfcd0eefe..ed447c384 100644 --- 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 @@ -2,6 +2,27 @@ 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.** 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 index c63e3fd40..2cb3f114e 100644 --- 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 @@ -2,6 +2,20 @@ 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 | 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 index 110c63141..53a0d60c6 100644 --- 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 @@ -2,6 +2,25 @@ 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| 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 index 47dad0792..95f59dec2 100644 --- 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 @@ -2,6 +2,27 @@ 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 | 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 index 3e7740ed4..8adcb3f56 100644 --- 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 @@ -2,6 +2,27 @@ 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 | 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 index fb3f34286..581ff6cdb 100644 --- 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 @@ -2,6 +2,27 @@ 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; 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 index 34f80a211..9f0fcc088 100644 --- 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 @@ -2,6 +2,28 @@ 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 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 index 132e2a417..8c8b135a9 100644 --- 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 @@ -2,6 +2,21 @@ 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.** 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 index 9aee13885..490549e94 100644 --- 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 @@ -2,6 +2,29 @@ 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 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 index 82b8ffa88..cfd1f380a 100644 --- 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 @@ -2,6 +2,27 @@ 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; 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 index 5ece5410f..71823a4b6 100644 --- 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 @@ -2,6 +2,28 @@ 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** 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 index 3898dfc62..e8af8cac0 100644 --- 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 @@ -2,6 +2,18 @@ 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| 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 index 82fdec2f1..0d99a891f 100644 --- 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 @@ -2,6 +2,23 @@ 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| 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 index 601edcdeb..90022579d 100644 --- 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 @@ -2,6 +2,24 @@ 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 | Results: 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 index 8d1ef12a3..1f04bb0f8 100644 --- 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 @@ -2,6 +2,18 @@ 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 (|) 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 index ca7421a06..12dcca6a4 100644 --- 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 @@ -2,6 +2,18 @@ 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 | diff --git a/crates/pgls_completions/src/test_helper.rs b/crates/pgls_completions/src/test_helper.rs index a4d9fe201..7613f2815 100644 --- a/crates/pgls_completions/src/test_helper.rs +++ b/crates/pgls_completions/src/test_helper.rs @@ -5,6 +5,7 @@ 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}; @@ -238,7 +239,7 @@ impl TestCompletionsCase { pub(crate) fn inside_static_statement(mut self, it: &str) -> Self { assert!(it.contains("")); - self.surrounding_statement = it.trim().to_string(); + self.surrounding_statement = unindent(it); self } @@ -596,8 +597,17 @@ impl<'a> TestCompletionsSuite<'a> { 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) @@ -609,8 +619,6 @@ impl<'a> TestCompletionsSuite<'a> { .set_language(&pgls_treesitter_grammar::LANGUAGE.into()) .expect("Problem with TreeSitter Grammar"); - let mut final_snapshot = String::new(); - let has_more_than_one_case = self.cases.len() > 1; for (idx, additional) in self.cases.iter().enumerate() { From 09664c1bb76ba2d6e9c273fa8ec9bcd28a0043fe Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 9 Nov 2025 09:44:24 +0100 Subject: [PATCH 9/9] ok --- crates/pgls_completions/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pgls_completions/Cargo.toml b/crates/pgls_completions/Cargo.toml index 6061faf60..43c0c3f4d 100644 --- a/crates/pgls_completions/Cargo.toml +++ b/crates/pgls_completions/Cargo.toml @@ -32,10 +32,10 @@ tokio = { version = "1.41.1", features = ["full"] } [dev-dependencies] criterion.workspace = true -unindent = "0.2.4" insta.workspace = true pgls_test_utils.workspace = true regex = "1.12.2" +unindent = "0.2.4" [lib]