diff --git a/CHANGELOG.md b/CHANGELOG.md index fee73404..66b8e71a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,9 +69,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 We now check files in parallel using rayon, which gives a nice speed up of 22-55%. +## Fixed + - lexer/ide: fix lexing of non-ascii + fix case folding in goto def (#1101) - Previously we case folding all of the characters in our unquoted identifiers + Previously we case folded all of the characters in our unquoted identifiers including unicode. This differed from Postgres behavior which only folded ascii. Now we correctly error instead of resolving in the following: diff --git a/crates/squawk_ide/src/ast_nav.rs b/crates/squawk_ide/src/ast_nav.rs index cd7e3853..b01d97ab 100644 --- a/crates/squawk_ide/src/ast_nav.rs +++ b/crates/squawk_ide/src/ast_nav.rs @@ -38,6 +38,18 @@ pub(crate) fn find_cte_with_table( None } +pub(crate) fn iter_values_columns(values: &ast::Values) -> impl Iterator { + values + .row_list() + .into_iter() + .flat_map(|rl| rl.rows().take(1)) + .flat_map(|r| r.exprs().enumerate()) + .map(|(idx, expr)| { + let name = Name::from_string(format!("column{}", idx + 1)); + (name, expr) + }) +} + #[derive(Debug)] pub(crate) enum ParentQuery { Select(ast::Select), diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index 4b08cb92..f5d802a9 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -50,7 +50,7 @@ impl Binder { name: &Name, kind: SymbolKind, position: TextSize, - schema: &Option, + schema: Option<&Schema>, ) -> Option { let symbols = self.scope.get(name)?; @@ -75,7 +75,7 @@ impl Binder { name: &Name, kind: SymbolKind, position: TextSize, - schema: &Option, + schema: Option<&Schema>, params: Option<&[Name]>, ) -> Option { let symbols = self.scope.get(name)?; @@ -107,7 +107,7 @@ impl Binder { name: &Name, kind: SymbolKind, position: TextSize, - schema: &Option, + schema: Option<&Schema>, table: &Option, ) -> Option { let symbols = self.scope.get(name)?; diff --git a/crates/squawk_ide/src/collect.rs b/crates/squawk_ide/src/collect.rs index 067f2b5f..16605302 100644 --- a/crates/squawk_ide/src/collect.rs +++ b/crates/squawk_ide/src/collect.rs @@ -42,10 +42,15 @@ fn columns_from_create_table_impl( ast_nav::CreateTableArg::Inherits(path) => { if let Some((schema, table_name)) = name::schema_and_name_path(&path) { let position = path.syntax().text_range().start(); - if let Some(ResolvedTableName::Table(parent_table)) = - resolve_table_name(db, file, &table_name, &schema, position) + if let Some((lookup_file, resolved)) = + resolve_table_name(db, file, &table_name, schema.as_ref(), position) { - columns_from_create_table_impl(db, file, &parent_table, columns, depth + 1); + columns.extend(resolved_to_column_ptrs( + db, + lookup_file, + resolved, + depth + 1, + )); } } } @@ -60,10 +65,15 @@ fn columns_from_create_table_impl( && let Some((schema, table_name)) = name::schema_and_name_path(&path) { let position = path.syntax().text_range().start(); - if let Some(ResolvedTableName::Table(source_table)) = - resolve_table_name(db, file, &table_name, &schema, position) + if let Some((lookup_file, resolved)) = + resolve_table_name(db, file, &table_name, schema.as_ref(), position) { - columns_from_create_table_impl(db, file, &source_table, columns, depth + 1); + columns.extend(resolved_to_column_ptrs( + db, + lookup_file, + resolved, + depth + 1, + )); } } } @@ -72,6 +82,41 @@ fn columns_from_create_table_impl( } } +fn resolved_to_column_ptrs( + db: &dyn Db, + file: File, + resolved: ResolvedTableName, + depth: usize, +) -> Vec<(Name, Option)> { + match resolved { + ResolvedTableName::Table(parent_table) => { + let mut cols = vec![]; + columns_from_create_table_impl(db, file, &parent_table, &mut cols, depth); + cols + } + ResolvedTableName::TableAs(create_table_as) => { + select_columns_with_types(db, file, &create_table_as.query()) + .into_iter() + .map(|(name, _ty)| (name, None)) + .collect() + } + ResolvedTableName::SelectInto(select_into) => select_into + .select_clause() + .and_then(|c| c.target_list()) + .map(|t| target_list_columns_with_types(&t)) + .unwrap_or_default() + .into_iter() + .map(|(name, _ty)| (name, None)) + .collect(), + ResolvedTableName::View(create_view_like) => { + view_like_columns_with_types(&create_view_like) + .into_iter() + .map(|(name, _ty)| (name, None)) + .collect() + } + } +} + pub(crate) fn table_columns( db: &dyn Db, file: File, @@ -99,10 +144,15 @@ fn table_columns_impl( ast_nav::CreateTableArg::Inherits(path) => { if let Some((schema, table_name)) = name::schema_and_name_path(&path) { let position = path.syntax().text_range().start(); - if let Some(ResolvedTableName::Table(parent_table)) = - resolve_table_name(db, file, &table_name, &schema, position) + if let Some((lookup_file, resolved)) = + resolve_table_name(db, file, &table_name, schema.as_ref(), position) { - columns.extend(table_columns_impl(db, file, &parent_table, depth + 1)); + columns.extend(resolved_to_columns_with_types( + db, + lookup_file, + resolved, + depth + 1, + )); } } } @@ -117,10 +167,15 @@ fn table_columns_impl( && let Some((schema, table_name)) = name::schema_and_name_path(&path) { let position = path.syntax().text_range().start(); - if let Some(ResolvedTableName::Table(source_table)) = - resolve_table_name(db, file, &table_name, &schema, position) + if let Some((lookup_file, resolved)) = + resolve_table_name(db, file, &table_name, schema.as_ref(), position) { - columns.extend(table_columns_impl(db, file, &source_table, depth + 1)); + columns.extend(resolved_to_columns_with_types( + db, + lookup_file, + resolved, + depth + 1, + )); } } } @@ -131,6 +186,30 @@ fn table_columns_impl( columns } +fn resolved_to_columns_with_types( + db: &dyn Db, + file: File, + resolved: ResolvedTableName, + depth: usize, +) -> Vec<(Name, Option)> { + match resolved { + ResolvedTableName::Table(parent_table) => { + table_columns_impl(db, file, &parent_table, depth) + } + ResolvedTableName::TableAs(create_table_as) => { + select_columns_with_types(db, file, &create_table_as.query()) + } + ResolvedTableName::SelectInto(select_into) => select_into + .select_clause() + .and_then(|c| c.target_list()) + .map(|t| target_list_columns_with_types(&t)) + .unwrap_or_default(), + ResolvedTableName::View(create_view_like) => { + view_like_columns_with_types(&create_view_like) + } + } +} + pub(crate) fn create_table_as_columns_with_types( create_table_as: &ast::CreateTableAs, ) -> Vec<(Name, Option)> { @@ -143,6 +222,96 @@ pub(crate) fn create_table_as_columns_with_types( .unwrap_or_default() } +// TODO: merge with select_variant_columns_with_types +fn select_columns_with_types( + db: &dyn Db, + file: File, + query: &Option, +) -> Vec<(Name, Option)> { + let Some(query) = query else { + return vec![]; + }; + match query { + ast::SelectVariant::Select(select) => { + let Some(target_list) = select.select_clause().and_then(|c| c.target_list()) else { + return vec![]; + }; + let from_clause = select.from_clause(); + let mut columns = vec![]; + for target in target_list.targets() { + if let Some((col_name, _)) = ColumnName::from_target(target.clone()) + && let Some(col_name_str) = col_name.to_string() + { + let ty = target.expr().and_then(|e| infer_type_from_expr(&e)); + columns.push((Name::from_string(col_name_str), ty)); + } else if target.star_token().is_some() + && let Some(from_clause) = &from_clause + { + columns.extend(columns_for_star_from_clause(db, file, from_clause)); + } + } + columns + } + ast::SelectVariant::Values(values) => columns_from_values(values), + ast::SelectVariant::Table(table) => { + let Some(path) = table.relation_name().and_then(|r| r.path()) else { + return vec![]; + }; + let Some((schema, table_name)) = name::schema_and_name_path(&path) else { + return vec![]; + }; + let name_ref = path.segment().and_then(|s| s.name_ref()); + let position = table.syntax().text_range().start(); + // Try CTE resolution first since resolve_table_name doesn't handle CTEs + if let Some((ptr, kind)) = resolve_table_like( + db, + file, + name_ref.as_ref(), + &table_name, + schema.as_ref(), + position, + ) { + let tree = parse(db, file).tree(); + let node = ptr.to_node(tree.syntax()); + match kind { + LocationKind::Table => { + if let Some(with_table) = node.ancestors().find_map(ast::WithTable::cast) { + return with_table_columns_with_types(db, file, with_table); + } + if let Some(t) = node.ancestors().find_map(ast::CreateTableLike::cast) { + return table_columns(db, file, &t); + } + } + LocationKind::View => { + if let Some(v) = node.ancestors().find_map(ast::CreateViewLike::cast) { + return view_like_columns_with_types(&v); + } + } + _ => (), + } + } + // Fall back to builtins for schema-qualified names + if let Some((lookup_file, resolved)) = + resolve_table_name(db, file, &table_name, schema.as_ref(), position) + { + return resolved_to_columns_with_types(db, lookup_file, resolved, 0); + } + vec![] + } + ast::SelectVariant::SelectInto(select_into) => select_into + .select_clause() + .and_then(|c| c.target_list()) + .map(|t| target_list_columns_with_types(&t)) + .unwrap_or_default(), + ast::SelectVariant::ParenSelect(nested) => { + paren_select_columns_with_types(db, file, nested) + } + ast::SelectVariant::CompoundSelect(compound) => { + select_columns_with_types(db, file, &compound.lhs()) + } + } +} + fn columns_from_returning_clause_with_types( query: &ast::WithQuery, ) -> Option)>> { @@ -208,13 +377,6 @@ pub(crate) fn view_like_columns_with_types( results } -fn with_table_column_names(db: &dyn Db, file: File, with_table: ast::WithTable) -> Vec { - with_table_columns_with_types(db, file, with_table) - .into_iter() - .map(|(name, _)| name) - .collect() -} - pub(crate) fn with_table_columns_with_types( db: &dyn Db, file: File, @@ -257,17 +419,7 @@ fn with_table_query_columns_with_types( }; if let ast::WithQuery::Values(values) = query { - let mut results = vec![]; - if let Some(row_list) = values.row_list() - && let Some(first_row) = row_list.rows().next() - { - for (idx, expr) in first_row.exprs().enumerate() { - let name = Name::from_string(format!("column{}", idx + 1)); - let ty = infer_type_from_expr(&expr); - results.push((name, ty)); - } - } - return results; + return columns_from_values(&values); } if let Some(columns) = columns_from_returning_clause_with_types(&query) { @@ -313,6 +465,15 @@ fn with_table_query_columns_with_types( columns } +fn columns_from_values(values: &ast::Values) -> Vec<(Name, Option)> { + let mut results = vec![]; + for (name, expr) in ast_nav::iter_values_columns(values) { + let ty = infer_type_from_expr(&expr); + results.push((name, ty)); + } + return results; +} + fn columns_for_star_from_clause( db: &dyn Db, file: File, @@ -444,19 +605,7 @@ fn select_variant_columns_with_types( let tree = parse(db, file).tree(); let root = tree.syntax(); match select_variant { - ast::SelectVariant::Values(values) => { - let mut results = vec![]; - if let Some(row_list) = values.row_list() - && let Some(first_row) = row_list.rows().next() - { - for (idx, expr) in first_row.exprs().enumerate() { - let name = Name::from_string(format!("column{}", idx + 1)); - let ty = infer_type_from_expr(&expr); - results.push((name, ty)); - } - } - results - } + ast::SelectVariant::Values(values) => columns_from_values(values), ast::SelectVariant::Select(select) => { let Some(select_clause) = select.select_clause() else { return vec![]; @@ -493,9 +642,14 @@ fn select_variant_columns_with_types( }; let name_ref = path.segment().and_then(|segment| segment.name_ref()); let position = table.syntax().text_range().start(); - let Some((ptr, kind)) = - resolve_table_like(db, file, name_ref.as_ref(), &table_name, &schema, position) - else { + let Some((ptr, kind)) = resolve_table_like( + db, + file, + name_ref.as_ref(), + &table_name, + schema.as_ref(), + position, + ) else { return vec![]; }; let node = ptr.to_node(root); @@ -507,10 +661,7 @@ fn select_variant_columns_with_types( .unwrap_or_default(), LocationKind::Table => { if let Some(with_table) = node.ancestors().find_map(ast::WithTable::cast) { - return with_table_column_names(db, file, with_table) - .into_iter() - .map(|name| (name, None)) - .collect(); + return with_table_columns_with_types(db, file, with_table); } node.ancestors() .find_map(ast::CreateTableLike::cast) @@ -536,12 +687,16 @@ pub(crate) fn star_column_names(db: &dyn Db, file: File, table_ptr: &SyntaxNodeP .filter_map(|column| column.name().map(|name| Name::from_node(&name))) .collect(), Some(ast_nav::ParentSouce::WithTable(with_table)) => { - let columns = with_table_column_names(db, file, with_table.clone()); - if !columns.is_empty() { - return columns; + for file in [file, builtins_file(db)] { + let columns: Vec<_> = with_table_columns_with_types(db, file, with_table.clone()) + .into_iter() + .map(|(name, _)| name) + .collect(); + if !columns.is_empty() { + return columns; + } } - - with_table_column_names(db, builtins_file(db), with_table) + vec![] } Some(ast_nav::ParentSouce::CreateTable(create_table)) => { table_columns(db, file, &create_table) diff --git a/crates/squawk_ide/src/completion.rs b/crates/squawk_ide/src/completion.rs index 4ac81ec1..b6475500 100644 --- a/crates/squawk_ide/src/completion.rs +++ b/crates/squawk_ide/src/completion.rs @@ -61,7 +61,7 @@ fn select_completions( let schema = schema_qualifier_at_token(token); let position = token.text_range().start(); - completions.extend(function_completions(db, file, &schema, position)); + completions.extend(function_completions(db, file, schema.as_ref(), position)); let tables = binder.all_symbols_by_kind(SymbolKind::Table, schema.as_ref()); completions.extend(tables.into_iter().map(|name| CompletionItem { @@ -270,7 +270,7 @@ fn limit_completions(db: &dyn Db, file: File, token: &SyntaxToken) -> Vec Vec, + schema: Option<&Schema>, position: TextSize, ) -> Vec { let binder = bind(db, file); binder - .all_symbols_by_kind(SymbolKind::Function, schema.as_ref()) + .all_symbols_by_kind(SymbolKind::Function, schema) .into_iter() .map(|name| CompletionItem { label: format!("{name}()"), @@ -639,7 +639,7 @@ fn delete_expr_completions( completions.extend(functions.into_iter().map(|name| CompletionItem { label: name.to_string(), kind: CompletionItemKind::Function, - detail: function_detail(db, file, name, &schema, position), + detail: function_detail(db, file, name, schema.as_ref(), position), insert_text: None, insert_text_format: None, trigger_completion_after_insert: false, @@ -650,7 +650,7 @@ fn delete_expr_completions( completions.extend(functions.into_iter().map(|name| CompletionItem { label: format!("{name}()"), kind: CompletionItemKind::Function, - detail: function_detail(db, file, name, &schema, position), + detail: function_detail(db, file, name, schema.as_ref(), position), insert_text: None, insert_text_format: None, trigger_completion_after_insert: false, @@ -669,12 +669,15 @@ fn delete_expr_completions( } let schema = name::schema_name(&path); - if let Some(table_ptr) = - binder.lookup_with(&delete_table_name, SymbolKind::Table, position, &schema) - && let Some(create_table) = table_ptr - .to_node(source_file.syntax()) - .ancestors() - .find_map(ast::CreateTableLike::cast) + if let Some(table_ptr) = binder.lookup_with( + &delete_table_name, + SymbolKind::Table, + position, + schema.as_ref(), + ) && let Some(create_table) = table_ptr + .to_node(source_file.syntax()) + .ancestors() + .find_map(ast::CreateTableLike::cast) { let columns = collect::table_columns(db, file, &create_table); completions.extend(columns.into_iter().map(|(name, ty)| CompletionItem { @@ -836,7 +839,7 @@ fn function_detail( db: &dyn Db, file: File, function_name: &Name, - schema: &Option, + schema: Option<&Schema>, position: TextSize, ) -> Option { let binder = bind(db, file); @@ -1137,6 +1140,77 @@ select $0 from t; "); } + #[test] + fn completion_after_select_create_table_inherits_builtin() { + assert_snapshot!(completions(" +create table t () +inherits (information_schema.sql_features); +select $0 from t; +"), @" + label | kind | detail + --------------------+----------+---------------- + comments | Column | character_data + feature_id | Column | character_data + feature_name | Column | character_data + is_supported | Column | yes_or_no + is_verified_by | Column | character_data + sub_feature_id | Column | character_data + sub_feature_name | Column | character_data + t | Table | + * | Operator | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | + "); + } + + #[test] + fn completion_after_select_create_table_inherits_create_table_as() { + assert_snapshot!(completions(" +create table parent as select 1 a, 'x'::text b; +create table child (c int) inherits (parent); +select $0 from child; +"), @" + label | kind | detail + --------------------+----------+--------- + a | Column | integer + b | Column | text + c | Column | int + child | Table | + parent | Table | + * | Operator | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | + "); + } + + #[test] + fn completion_after_select_create_table_like_select_into() { + assert_snapshot!(completions(" +select 1 a, 'x'::text b into parent; +create table child (like parent); +select $0 from child; +"), @" + label | kind | detail + --------------------+----------+--------- + a | Column | integer + b | Column | text + child | Table | + parent | Table | + * | Operator | + public | Schema | + pg_catalog | Schema | + pg_temp | Schema | + pg_toast | Schema | + information_schema | Schema | + "); + } + #[test] fn completion_select_table_qualified() { assert_snapshot!(completions(" diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index ff3de63c..a37d98df 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -2127,6 +2127,22 @@ create table t ( "); } + #[test] + fn goto_create_table_like_view() { + assert_snapshot!(goto(" +create view v as select 1 a, 2 b; +create table t (like v); +select a$0 from t; +"), @" + ╭▸ + 2 │ create view v as select 1 a, 2 b; + │ ─ 2. destination + 3 │ create table t (like v); + 4 │ select a from t; + ╰╴ ─ 1. source + "); + } + #[test] fn goto_create_table_inherits() { assert_snapshot!(goto(" @@ -2143,6 +2159,26 @@ inherits (foo.bar, bar$0, buzz); "); } + #[test] + fn goto_create_table_inherits_builtin() { + assert_snapshot!(goto(" +create table t () +inherits (information_schema.sql_features); +select feature_name$0 from t; +"), @" + ╭▸ current.sql:4:19 + │ + 4 │ select feature_name from t; + │ ─ 1. source + ╰╴ + + ╭▸ builtins.sql:437:3 + │ + 437 │ feature_name information_schema.character_data, + ╰╴ ──────────── 2. destination + "); + } + #[test] fn goto_create_table_like_clause_columns() { assert_snapshot!(goto(" @@ -2781,6 +2817,52 @@ select a$0 from t; "); } + #[test] + fn goto_create_table_as_table() { + assert_snapshot!(goto(" +create table t(a bigint); +create table u as table t; +select a$0 from u; +"), @" + ╭▸ + 2 │ create table t(a bigint); + │ ─ 2. destination + 3 │ create table u as table t; + 4 │ select a from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_table_as_select_star() { + assert_snapshot!(goto(" +create table t(a bigint); +create table u as select * from t; +select a$0 from u; +"), @" + ╭▸ + 2 │ create table t(a bigint); + │ ─ 2. destination + 3 │ create table u as select * from t; + 4 │ select a from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_table_as_values() { + assert_snapshot!(goto(" +create table k as values (1, 2); +select column1$0 from k; +"), @" + ╭▸ + 2 │ create table k as values (1, 2); + │ ─ 2. destination + 3 │ select column1 from k; + ╰╴ ─ 1. source + "); + } + #[test] fn goto_select_from_create_table_as() { assert_snapshot!(goto(" @@ -2795,6 +2877,20 @@ select a from t$0; "); } + #[test] + fn goto_like_view_definition() { + assert_snapshot!(goto(" +create view v as select 1 a; +create table t (like v$0); +"), @" + ╭▸ + 2 │ create view v as select 1 a; + │ ─ 2. destination + 3 │ create table t (like v); + ╰╴ ─ 1. source + "); + } + #[test] fn goto_view_with_explicit_column_list() { assert_snapshot!(goto(" diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index 666e4aab..29c8aea9 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -269,15 +269,10 @@ fn hover_column(db: &dyn Db, definitions: &[Location]) -> Option { } fn column_name_from_node(column_name_node: &SyntaxNode) -> Option { - if column_name_node - .ancestors() - .any(|ancestor| ast::Values::can_cast(ancestor.kind())) - { - let row = column_name_node.ancestors().find_map(ast::Row::cast)?; - let idx = row - .exprs() - .position(|expr| expr.syntax() == column_name_node)?; - Some(Name::from_string(format!("column{}", idx + 1))) + if let Some(values) = column_name_node.ancestors().find_map(ast::Values::cast) { + ast_nav::iter_values_columns(&values) + .find(|(_, expr)| expr.syntax() == column_name_node) + .map(|(name, _)| name) } else if let Some(name) = ast::Name::cast(column_name_node.clone()) { Some(Name::from_node(&name)) } else { @@ -294,10 +289,20 @@ fn format_hover_for_column_ptr(db: &dyn Db, def: Location) -> Option { let cte_name = with_table.name()?; let column_name = column_name_from_node(def_node)?; let table_name = Name::from_node(&cte_name); - return Some(Hover::snippet(ColumnHover::table_column( - &table_name.to_string(), - &column_name.to_string(), - ))); + let ty = collect::with_table_columns_with_types(db, def.file, with_table) + .into_iter() + .find(|(name, _)| *name == column_name) + .and_then(|(_, ty)| ty); + return Some(Hover::snippet(match ty { + Some(ty) => ColumnHover::table_column_type( + &table_name.to_string(), + &column_name.to_string(), + &ty.to_string(), + ), + None => { + ColumnHover::table_column(&table_name.to_string(), &column_name.to_string()) + } + })); } ast_nav::ParentSouce::ParenSelect(paren_select) => { // Qualified access like `t.a` @@ -478,6 +483,7 @@ fn hover_qualified_star(db: &dyn Db, file: File, field_expr: ast::FieldExpr) -> fn hover_unqualified_star(db: &dyn Db, file: File, target: ast::Target) -> Option { let mut results = hover_unqualified_star_with_binder(db, file, &target); + // TODO: this needs to be updated when we support searching extensions if results.is_empty() && target_has_schema_qualified_from_item(&target) { results = hover_unqualified_star_with_binder(db, builtins_file(db), &target); } @@ -1429,7 +1435,7 @@ fn qualified_star_table_ptr( file, name_ref.as_ref(), &table_name, - &schema, + schema.as_ref(), position, ) { return Some(table_like_ptr); @@ -1458,9 +1464,14 @@ fn table_or_view_or_cte_ptrs( let mut results = vec![]; let name_ref = path.segment().and_then(|x| x.name_ref()); - if let Some((table_like_ptr, _kind)) = - resolve::resolve_table_like(db, file, name_ref.as_ref(), &table_name, &schema, position) - { + if let Some((table_like_ptr, _kind)) = resolve::resolve_table_like( + db, + file, + name_ref.as_ref(), + &table_name, + schema.as_ref(), + position, + ) { results.push(table_like_ptr); } @@ -3009,8 +3020,8 @@ select t$0 from t; assert_snapshot!(check_hover(" with t as (select 1 a) select a$0 from t; -"), @r" - hover: column t.a +"), @" + hover: column t.a integer ╭▸ 3 │ select a from t; ╰╴ ─ hover @@ -3022,8 +3033,8 @@ select a$0 from t; assert_snapshot!(check_hover(" with t as (select 1 a, 2 b) select b$0 from t; -"), @r" - hover: column t.b +"), @" + hover: column t.b integer ╭▸ 3 │ select b from t; ╰╴ ─ hover @@ -3035,8 +3046,8 @@ select b$0 from t; assert_snapshot!(check_hover(" with t(a) as (select 1) select a$0 from t; -"), @r" - hover: column t.a +"), @" + hover: column t.a integer ╭▸ 3 │ select a from t; ╰╴ ─ hover @@ -3091,8 +3102,8 @@ with t as ( values (1, 2), (3, 4) ) select column1$0, column2 from t; -"), @r" - hover: column t.column1 +"), @" + hover: column t.column1 integer ╭▸ 5 │ select column1, column2 from t; ╰╴ ─ hover @@ -3106,8 +3117,8 @@ with t as ( values (1, 2), (3, 4) ) select column1, column2$0 from t; -"), @r" - hover: column t.column2 +"), @" + hover: column t.column2 integer ╭▸ 5 │ select column1, column2 from t; ╰╴ ─ hover @@ -3121,8 +3132,8 @@ with t as ( values (1), (2), (3) ) select column1$0 from t; -"), @r" - hover: column t.column1 +"), @" + hover: column t.column1 integer ╭▸ 5 │ select column1 from t; ╰╴ ─ hover @@ -3136,8 +3147,8 @@ with t as ( values (1, 2), (3, 4) ) select COLUMN1$0, COLUMN2 from t; -"), @r" - hover: column t.column1 +"), @" + hover: column t.column1 integer ╭▸ 5 │ select COLUMN1, COLUMN2 from t; ╰╴ ─ hover @@ -3359,9 +3370,9 @@ select *$0 from (select a from t); assert_snapshot!(check_hover(" with t as (select 1 a, 2 b) select *$0 from (table t); -"), @r" - hover: column a - column b +"), @" + hover: column a integer + column b integer ╭▸ 3 │ select * from (table t); ╰╴ ─ hover @@ -3429,7 +3440,7 @@ select "?column?"$0 from v; with x as (select 1) select "?column?"$0 from x; "#), @r#" - hover: column x.?column? + hover: column x.?column? integer ╭▸ 3 │ select "?column?" from x; ╰╴ ─ hover @@ -3915,8 +3926,8 @@ with new_data as ( select 1 as id, 'new@example.com' as email ) update users set email = new_data.email$0 from new_data where users.id = new_data.id; -"), @r" - hover: column new_data.email +"), @" + hover: column new_data.email text ╭▸ 6 │ update users set email = new_data.email from new_data where users.id = new_data.id; ╰╴ ─ hover @@ -3931,8 +3942,8 @@ with new_data as ( select 1 as id, 'new@example.com' as email ) update users set email = new_data.email from new_data where new_data.id$0 = users.id; -"), @r" - hover: column new_data.id +"), @" + hover: column new_data.id integer ╭▸ 6 │ update users set email = new_data.email from new_data where new_data.id = users.id; ╰╴ ─ hover @@ -4587,6 +4598,76 @@ select *$0 from u; "); } + #[test] + fn hover_create_table_inherits_builtin_star() { + assert_snapshot!(check_hover_info(" +create table t () +inherits (information_schema.sql_features); +select *$0 from t; +").snippet, @" + column public.t.feature_id character_data + column public.t.feature_name character_data + column public.t.sub_feature_id character_data + column public.t.sub_feature_name character_data + column public.t.is_supported yes_or_no + column public.t.is_verified_by character_data + column public.t.comments character_data + "); + } + + #[test] + fn hover_create_table_like_builtin_star() { + assert_snapshot!(check_hover_info(" +create table t (like information_schema.sql_features); +select *$0 from t; +").snippet, @" + column public.t.feature_id character_data + column public.t.feature_name character_data + column public.t.sub_feature_id character_data + column public.t.sub_feature_name character_data + column public.t.is_supported yes_or_no + column public.t.is_verified_by character_data + column public.t.comments character_data + "); + } + + #[test] + fn hover_create_table_inherits_create_table_as_star() { + assert_snapshot!(check_hover_info(" +create table parent as select 1 a, 'x'::text b; +create table child (c int) inherits (parent); +select *$0 from child; +").snippet, @" + column public.child.a integer + column public.child.b text + column public.child.c int + "); + } + + #[test] + fn hover_create_table_like_select_into_star() { + assert_snapshot!(check_hover_info(" +select 1 a, 'x'::text b into parent; +create table child (like parent); +select *$0 from child; +").snippet, @" + column public.child.a integer + column public.child.b text + "); + } + + #[test] + fn hover_create_table_like_view_star() { + assert_snapshot!(check_hover_info(" +create view parent as select 1 a, 'x'::text b; +create table child (like parent); +select *$0 from child; +").snippet, @" + column public.child.a integer + column public.child.b text + "); + } + #[test] fn hover_create_table_inherits_column() { assert_snapshot!(check_hover(" @@ -4605,6 +4686,20 @@ select a$0 from u; "); } + #[test] + fn hover_create_table_inherits_builtin_column() { + assert_snapshot!(check_hover(" +create table t () +inherits (information_schema.sql_features); +select feature_name$0 from t; +"), @" + hover: column information_schema.sql_features.feature_name information_schema.character_data + ╭▸ + 4 │ select feature_name from t; + ╰╴ ─ hover + "); + } + #[test] fn hover_create_table_inherits_local_column() { assert_snapshot!(check_hover(" diff --git a/crates/squawk_ide/src/inlay_hints.rs b/crates/squawk_ide/src/inlay_hints.rs index 4ba5083b..4081c08f 100644 --- a/crates/squawk_ide/src/inlay_hints.rs +++ b/crates/squawk_ide/src/inlay_hints.rs @@ -427,6 +427,49 @@ insert into u select 1, 2, 3; "); } + #[test] + fn insert_table_inherits_builtin_values() { + assert_snapshot!(check_inlay_hints(" +create table t () +inherits (information_schema.sql_features); +insert into t values (1, 2, 3, 4, 5, 6, 7); +"), @" + inlay hints: + ╭▸ + 4 │ …values (feature_id: 1, feature_name: 2, sub_feature_id: 3, sub_feature_name: 4, is_supported: 5, is_verified_by: 6, comments: 7); + ╰╴ ──────────── ────────────── ──────────────── ────────────────── ────────────── ──────────────── ────────── + "); + } + + #[test] + fn insert_table_inherits_create_table_as_values() { + assert_snapshot!(check_inlay_hints(" +create table parent as select 1 a, 'x'::text b; +create table child (c int) inherits (parent); +insert into child values (1, 2, 3); +"), @" + inlay hints: + ╭▸ + 4 │ insert into child values (a: 1, b: 2, c: 3); + ╰╴ ─── ─── ─── + "); + } + + #[test] + fn insert_table_inherits_create_table_as_select_star() { + assert_snapshot!(check_inlay_hints(" +create table base (a int, b text); +create table parent as select * from base; +create table child (c int) inherits (parent); +insert into child values (1, 2, 3); +"), @" + inlay hints: + ╭▸ + 5 │ insert into child values (a: 1, b: 2, c: 3); + ╰╴ ─── ─── ─── + "); + } + #[test] fn insert_table_like_select() { assert_snapshot!(check_inlay_hints(" @@ -453,4 +496,31 @@ insert into t select 1, 2; ╰╴ ─── ─── "); } + + #[test] + fn insert_table_like_builtin_values() { + assert_snapshot!(check_inlay_hints(" +create table t (like information_schema.sql_features); +insert into t values (1, 2, 3, 4, 5, 6, 7); +"), @" + inlay hints: + ╭▸ + 3 │ …values (feature_id: 1, feature_name: 2, sub_feature_id: 3, sub_feature_name: 4, is_supported: 5, is_verified_by: 6, comments: 7); + ╰╴ ──────────── ────────────── ──────────────── ────────────────── ────────────── ──────────────── ────────── + "); + } + + #[test] + fn insert_table_like_select_into_values() { + assert_snapshot!(check_inlay_hints(" +select 1 a, 'x'::text b into parent; +create table child (like parent); +insert into child values (1, 2); +"), @" + inlay hints: + ╭▸ + 4 │ insert into child values (a: 1, b: 2); + ╰╴ ─── ─── + "); + } } diff --git a/crates/squawk_ide/src/name.rs b/crates/squawk_ide/src/name.rs index 348348c9..f49a0d6d 100644 --- a/crates/squawk_ide/src/name.rs +++ b/crates/squawk_ide/src/name.rs @@ -100,6 +100,7 @@ pub(crate) fn schema_name(path: &ast::Path) -> Option { .map(|name_ref| Schema(Name::from_node(&name_ref))) } +// TODO: doesn't handle CTEs/subqueries/aliases pub(crate) fn schema_and_table_from_from_item( from_item: &ast::FromItem, ) -> Option<(Option, Name)> { diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index 851cf959..78e3d361 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -6,6 +6,7 @@ use squawk_syntax::{ ast::{self, AstNode}, }; +use crate::builtins::builtins_file; use crate::column_name::ColumnName; use crate::db::File; use crate::location::{Location, LocationKind}; @@ -50,7 +51,7 @@ pub(crate) fn resolve_name_ref( )]); } - let ptr = resolve_table_name_ptr(db, file, &table_name, &schema, position)?; + let ptr = resolve_table_name_ptr(db, file, &table_name, schema.as_ref(), position)?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -61,7 +62,9 @@ pub(crate) fn resolve_name_ref( let (schema, relation_name) = name::schema_and_table_name(name_ref)?; let position = name_ref.syntax().text_range().start(); - if let Some(ptr) = resolve_table_name_ptr(db, file, &relation_name, &schema, position) { + if let Some(ptr) = + resolve_table_name_ptr(db, file, &relation_name, schema.as_ref(), position) + { return Some(smallvec![Location::new( file, ptr.text_range(), @@ -69,7 +72,7 @@ pub(crate) fn resolve_name_ref( )]); } - let ptr = resolve_view_name_ptr(db, file, &relation_name, &schema, position)?; + let ptr = resolve_view_name_ptr(db, file, &relation_name, schema.as_ref(), position)?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -91,12 +94,27 @@ pub(crate) fn resolve_name_ref( // TODO: this should be one lookup let function_ptr = binder - .lookup_with(&function_name, SymbolKind::Function, position, &schema) + .lookup_with( + &function_name, + SymbolKind::Function, + position, + schema.as_ref(), + ) .or_else(|| { - binder.lookup_with(&function_name, SymbolKind::Procedure, position, &schema) + binder.lookup_with( + &function_name, + SymbolKind::Procedure, + position, + schema.as_ref(), + ) }) .or_else(|| { - binder.lookup_with(&function_name, SymbolKind::Aggregate, position, &schema) + binder.lookup_with( + &function_name, + SymbolKind::Aggregate, + position, + schema.as_ref(), + ) })?; let param_ptr = find_param_in_func_def(db, file, function_ptr, ¶m_name)?; @@ -138,15 +156,21 @@ pub(crate) fn resolve_name_ref( NameRefClass::FromTable => { let (schema, table_name) = name::schema_and_name(name_ref); let position = name_ref.syntax().text_range().start(); - let (ptr, kind) = - resolve_table_like(db, file, Some(name_ref), &table_name, &schema, position)?; + let (ptr, kind) = resolve_table_like( + db, + file, + Some(name_ref), + &table_name, + schema.as_ref(), + position, + )?; Some(smallvec![Location::new(file, ptr.text_range(), kind)]) } NameRefClass::Index => { let position = name_ref.syntax().text_range().start(); let index_name = Name::from_node(name_ref); let binder = bind(db, file); - let ptr = binder.lookup_with(&index_name, SymbolKind::Index, position, &None)?; + let ptr = binder.lookup_with(&index_name, SymbolKind::Index, position, None)?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -157,7 +181,7 @@ pub(crate) fn resolve_name_ref( let (schema, type_name) = name::schema_and_table_name(name_ref)?; let type_name = resolve_float_precision(name_ref, type_name); let position = name_ref.syntax().text_range().start(); - let ptr = resolve_type_name_ptr(db, file, &type_name, &schema, position)?; + let ptr = resolve_type_name_ptr(db, file, &type_name, schema.as_ref(), position)?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -167,7 +191,7 @@ pub(crate) fn resolve_name_ref( NameRefClass::View => { let (schema, view_name) = name::schema_and_table_name(name_ref)?; let position = name_ref.syntax().text_range().start(); - let ptr = resolve_view_name_ptr(db, file, &view_name, &schema, position)?; + let ptr = resolve_view_name_ptr(db, file, &view_name, schema.as_ref(), position)?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -194,8 +218,12 @@ pub(crate) fn resolve_name_ref( let (schema, sequence_name) = name::schema_and_table_name(name_ref)?; let position = name_ref.syntax().text_range().start(); let binder = bind(db, file); - let ptr = - binder.lookup_with(&sequence_name, SymbolKind::Sequence, position, &schema)?; + let ptr = binder.lookup_with( + &sequence_name, + SymbolKind::Sequence, + position, + schema.as_ref(), + )?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -222,7 +250,7 @@ pub(crate) fn resolve_name_ref( &trigger_name, SymbolKind::Trigger, position, - &schema, + schema.as_ref(), &Some(table_name), )?; Some(smallvec![Location::new( @@ -248,7 +276,7 @@ pub(crate) fn resolve_name_ref( &policy_name, SymbolKind::Policy, position, - &schema, + schema.as_ref(), &Some(table_name), )?; Some(smallvec![Location::new( @@ -276,7 +304,7 @@ pub(crate) fn resolve_name_ref( &property_graph_name, SymbolKind::PropertyGraph, position, - &schema, + schema.as_ref(), )?; Some(smallvec![Location::new( file, @@ -344,7 +372,7 @@ pub(crate) fn resolve_name_ref( let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; let (schema, table_name) = name::schema_and_name_path(&path)?; let position = name_ref.syntax().text_range().start(); - let ptr = resolve_table_name_ptr(db, file, &table_name, &schema, position)?; + let ptr = resolve_table_name_ptr(db, file, &table_name, schema.as_ref(), position)?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -409,7 +437,7 @@ pub(crate) fn resolve_name_ref( })?; let (schema, table_name) = name::schema_and_name_path(&on_table_path)?; let position = name_ref.syntax().text_range().start(); - let ptr = resolve_table_name_ptr(db, file, &table_name, &schema, position)?; + let ptr = resolve_table_name_ptr(db, file, &table_name, schema.as_ref(), position)?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -424,12 +452,25 @@ pub(crate) fn resolve_name_ref( let path = like_clause.path()?; let (schema, table_name) = name::schema_and_name_path(&path)?; let position = name_ref.syntax().text_range().start(); - let ptr = resolve_table_name_ptr(db, file, &table_name, &schema, position)?; - Some(smallvec![Location::new( - file, - ptr.text_range(), - LocationKind::Table - )]) + if let Some(ptr) = + resolve_table_name_ptr(db, file, &table_name, schema.as_ref(), position) + { + return Some(smallvec![Location::new( + file, + ptr.text_range(), + LocationKind::Table + )]); + } + if let Some(ptr) = + resolve_view_name_ptr(db, file, &table_name, schema.as_ref(), position) + { + return Some(smallvec![Location::new( + file, + ptr.text_range(), + LocationKind::View + )]); + } + None } NameRefClass::Function => { let function_sig = name_ref @@ -444,7 +485,7 @@ pub(crate) fn resolve_name_ref( db, file, &function_name, - &schema, + schema.as_ref(), params.as_deref(), position, ) @@ -462,7 +503,7 @@ pub(crate) fn resolve_name_ref( db, file, &aggregate_name, - &schema, + schema.as_ref(), params.as_deref(), position, ) @@ -480,7 +521,7 @@ pub(crate) fn resolve_name_ref( db, file, &procedure_name, - &schema, + schema.as_ref(), params.as_deref(), position, ) @@ -499,7 +540,7 @@ pub(crate) fn resolve_name_ref( db, file, &routine_name, - &schema, + schema.as_ref(), params.as_deref(), position, ) { @@ -510,7 +551,7 @@ pub(crate) fn resolve_name_ref( db, file, &routine_name, - &schema, + schema.as_ref(), params.as_deref(), position, ) { @@ -521,7 +562,7 @@ pub(crate) fn resolve_name_ref( db, file, &routine_name, - &schema, + schema.as_ref(), params.as_deref(), position, ) @@ -531,7 +572,7 @@ pub(crate) fn resolve_name_ref( let path = call.path()?; let (schema, procedure_name) = name::schema_and_name_path(&path)?; let position = name_ref.syntax().text_range().start(); - resolve_procedure(db, file, &procedure_name, &schema, None, position) + resolve_procedure(db, file, &procedure_name, schema.as_ref(), None, position) } NameRefClass::Schema => { let schema_name = Name::from_node(name_ref); @@ -546,13 +587,13 @@ pub(crate) fn resolve_name_ref( NameRefClass::FunctionCall => { let (schema, function_name) = name::schema_and_name(name_ref); let position = name_ref.syntax().text_range().start(); - resolve_function(db, file, &function_name, &schema, None, position) + resolve_function(db, file, &function_name, schema.as_ref(), None, position) } NameRefClass::ProcedureCall => { let (schema, procedure_name) = name::schema_and_name(name_ref); let position = name_ref.syntax().text_range().start(); - resolve_procedure(db, file, &procedure_name, &schema, None, position) + resolve_procedure(db, file, &procedure_name, schema.as_ref(), None, position) } NameRefClass::FunctionName => { let path_type = name_ref @@ -562,7 +603,7 @@ pub(crate) fn resolve_name_ref( let path = path_type.path()?; let (schema, function_name) = name::schema_and_name_path(&path)?; let position = name_ref.syntax().text_range().start(); - resolve_function(db, file, &function_name, &schema, None, position) + resolve_function(db, file, &function_name, schema.as_ref(), None, position) } NameRefClass::SelectFunctionCall => { let (schema, function_name) = name::schema_and_name(name_ref); @@ -570,14 +611,14 @@ pub(crate) fn resolve_name_ref( // functions take precedence if let Some(results) = - resolve_function(db, file, &function_name, &schema, None, position) + resolve_function(db, file, &function_name, schema.as_ref(), None, position) { return Some(results); } // aggregates take precedence over function-call-style column access if let Some(results) = - resolve_aggregate(db, file, &function_name, &schema, None, position) + resolve_aggregate(db, file, &function_name, schema.as_ref(), None, position) { return Some(results); } @@ -705,7 +746,7 @@ fn resolve_table_name_ptr( db: &dyn Db, file: File, table_name: &Name, - schema: &Option, + schema: Option<&Schema>, position: TextSize, ) -> Option { let binder = bind(db, file); @@ -716,7 +757,7 @@ fn resolve_type_name_ptr( db: &dyn Db, file: File, type_name: &Name, - schema: &Option, + schema: Option<&Schema>, position: TextSize, ) -> Option { let binder = bind(db, file); @@ -727,7 +768,7 @@ fn resolve_type_name_ptr( if schema.is_none() && let Some(fallback_name) = fallback_type_alias(type_name) { - return binder.lookup_with(&fallback_name, SymbolKind::Type, position, &None); + return binder.lookup_with(&fallback_name, SymbolKind::Type, position, None); } None @@ -740,7 +781,7 @@ pub(crate) fn resolve_type_ptr_from_type( position: TextSize, ) -> Option { let (schema, type_name) = name::schema_and_type_name(ty)?; - resolve_type_name_ptr(db, file, &type_name, &schema, position) + resolve_type_name_ptr(db, file, &type_name, schema.as_ref(), position) } fn fallback_type_alias(type_name: &Name) -> Option { @@ -774,7 +815,7 @@ fn resolve_view_name_ptr( db: &dyn Db, file: File, view_name: &Name, - schema: &Option, + schema: Option<&Schema>, position: TextSize, ) -> Option { let binder = bind(db, file); @@ -786,7 +827,7 @@ pub(crate) fn resolve_table_like( file: File, name_ref: Option<&ast::NameRef>, table_name: &Name, - schema: &Option, + schema: Option<&Schema>, position: TextSize, ) -> Option<(SyntaxNodePtr, LocationKind)> { if let Some(view_name_ptr) = resolve_view_name_ptr(db, file, table_name, schema, position) { @@ -811,7 +852,7 @@ fn resolve_for_kind_with_params( db: &dyn Db, file: File, name: &Name, - schema: &Option, + schema: Option<&Schema>, params: Option<&[Name]>, position: TextSize, kind: SymbolKind, @@ -838,14 +879,14 @@ fn resolve_special_keyword_as_function( })?; let function_name = Name::from_string(function_name); let position = name_ref.syntax().text_range().start(); - resolve_function(db, file, &function_name, &None, None, position) + resolve_function(db, file, &function_name, None, None, position) } fn resolve_function( db: &dyn Db, file: File, function_name: &Name, - schema: &Option, + schema: Option<&Schema>, params: Option<&[Name]>, position: TextSize, ) -> Option> { @@ -869,7 +910,7 @@ fn resolve_aggregate( db: &dyn Db, file: File, aggregate_name: &Name, - schema: &Option, + schema: Option<&Schema>, params: Option<&[Name]>, position: TextSize, ) -> Option> { @@ -893,7 +934,7 @@ fn resolve_procedure( db: &dyn Db, file: File, procedure_name: &Name, - schema: &Option, + schema: Option<&Schema>, params: Option<&[Name]>, position: TextSize, ) -> Option> { @@ -943,7 +984,7 @@ fn resolve_property_graph_column_ptr( if let Some(references_table) = ast::ReferencesTable::cast(column_list.syntax().parent()?) { let table_name = Name::from_node(&references_table.name_ref()?); let position = column_name_ref.syntax().text_range().start(); - return resolve_column_for_table(db, file, &table_name, &None, &column_name, position); + return resolve_column_for_table(db, file, &table_name, None, &column_name, position); } else if let Some(edge_table_def) = column_list .syntax() .ancestors() @@ -979,7 +1020,14 @@ fn resolve_column_for_path( let (schema, table_name) = name::schema_and_name_path(path)?; let position = path.syntax().text_range().start(); - resolve_column_for_table(db, file, &table_name, &schema, &column_name, position) + resolve_column_for_table( + db, + file, + &table_name, + schema.as_ref(), + &column_name, + position, + ) } fn resolve_select_qualified_column_table_name_ptr( @@ -1029,7 +1077,14 @@ fn resolve_select_qualified_column_table_name_ptr( && function_schema == explicit_schema { let position = table_name_ref.syntax().text_range().start(); - return resolve_function(db, file, &function_name, &function_schema, None, position); + return resolve_function( + db, + file, + &function_name, + function_schema.as_ref(), + None, + position, + ); } let (schema, table_name) = if let Some(schema) = explicit_schema { @@ -1043,7 +1098,7 @@ fn resolve_select_qualified_column_table_name_ptr( file, Some(table_name_ref), &table_name, - &schema, + schema.as_ref(), position, )?; Some(smallvec![Location::new(file, ptr.text_range(), kind)]) @@ -1248,18 +1303,25 @@ fn resolve_select_qualified_column_ptr( } } - resolve_column_for_table(db, file, &table_name, &schema, &column_name, position) + resolve_column_for_table( + db, + file, + &table_name, + schema.as_ref(), + &column_name, + position, + ) } fn resolve_column_for_table( db: &dyn Db, file: File, table_name: &Name, - schema: &Option, + schema: Option<&Schema>, column_name: &Name, position: TextSize, ) -> Option> { - let resolved = resolve_table_name(db, file, table_name, schema, position)?; + let (file, resolved) = resolve_table_name(db, file, table_name, schema, position)?; match resolved { ResolvedTableName::View(create_view) => { if let Some(ptr) = find_column_in_create_view_like(file, &create_view, column_name) { @@ -1279,7 +1341,9 @@ fn resolve_column_for_table( return resolve_function(db, file, column_name, schema, None, position); } ResolvedTableName::TableAs(create_table_as) => { - if let Some(ptr) = find_column_in_create_table_as(file, &create_table_as, column_name) { + if let Some(ptr) = + find_column_in_create_table_as(db, file, &create_table_as, column_name) + { return Some(ptr); } return resolve_function(db, file, column_name, schema, None, position); @@ -1305,34 +1369,38 @@ pub(crate) fn resolve_table_name( db: &dyn Db, file: File, table_name: &Name, - schema: &Option, + schema: Option<&Schema>, position: TextSize, -) -> Option { +) -> Option<(File, ResolvedTableName)> { use ResolvedTableName::*; - let (ptr, kind) = resolve_table_like(db, file, None, table_name, schema, position)?; - let tree = parse(db, file).tree(); - let root = tree.syntax(); - let node = ptr.to_node(root); - - match kind { - LocationKind::Table => { - if let Some(create_table) = node.ancestors().find_map(ast::CreateTableLike::cast) { - return Some(Table(create_table)); - } - if let Some(create_table_as) = node.ancestors().find_map(ast::CreateTableAs::cast) { - return Some(TableAs(create_table_as)); + for file in [file, builtins_file(db)] { + let Some((ptr, kind)) = resolve_table_like(db, file, None, table_name, schema, position) + else { + continue; + }; + let tree = parse(db, file).tree(); + let node = ptr.to_node(tree.syntax()); + match kind { + LocationKind::Table => { + if let Some(create_table) = node.ancestors().find_map(ast::CreateTableLike::cast) { + return Some((file, Table(create_table))); + } + if let Some(create_table_as) = node.ancestors().find_map(ast::CreateTableAs::cast) { + return Some((file, TableAs(create_table_as))); + } + if let Some(select_into) = node.ancestors().find_map(ast::SelectInto::cast) { + return Some((file, SelectInto(select_into))); + } } - if let Some(select_into) = node.ancestors().find_map(ast::SelectInto::cast) { - return Some(SelectInto(select_into)); + LocationKind::View => { + if let Some(view) = node.ancestors().find_map(ast::CreateViewLike::cast) { + return Some((file, View(view))); + } } - None + _ => (), } - LocationKind::View => node - .ancestors() - .find_map(ast::CreateViewLike::cast) - .map(View), - _ => None, } + None } fn resolve_merge_alias(name_ref: &ast::NameRef, table_name: &Name) -> Option { @@ -1450,7 +1518,7 @@ fn resolve_from_item_column_ptr( file, column_name_ref, &table_name, - &schema, + schema.as_ref(), &column_name, ) { return Some(ptr); @@ -1474,7 +1542,7 @@ fn resolve_column_from_table_or_view_or_cte( file: File, name_ref: &ast::NameRef, table_name: &Name, - schema: &Option, + schema: Option<&Schema>, column_name: &Name, ) -> Option> { resolve_column_from_table_or_view_or_cte_impl( @@ -1493,7 +1561,7 @@ fn resolve_column_from_table_or_view_or_cte_impl( file: File, name_ref: &ast::NameRef, table_name: &Name, - schema: &Option, + schema: Option<&Schema>, column_name: &Name, depth: u32, ) -> Option> { @@ -1535,7 +1603,7 @@ fn resolve_column_from_table_or_view_or_cte_impl( file, name_ref, &parent_table_name, - &parent_schema, + parent_schema.as_ref(), column_name, depth + 1, ); @@ -1557,7 +1625,7 @@ fn resolve_column_from_table_or_view_or_cte_impl( if let Some(create_table_as) = node.ancestors().find_map(ast::CreateTableAs::cast) { if let Some(cols) = - find_column_in_create_table_as(file, &create_table_as, column_name) + find_column_in_create_table_as(db, file, &create_table_as, column_name) { return Some(cols); } @@ -1625,7 +1693,14 @@ fn resolve_from_item_for_cte_star( && table_name == *cte_name { let position = name_ref.syntax().text_range().start(); - return resolve_column_for_table(db, file, &table_name, &schema, column_name, position); + return resolve_column_for_table( + db, + file, + &table_name, + schema.as_ref(), + column_name, + position, + ); } resolve_from_item_column_ptr(db, file, from_item, name_ref) @@ -1680,7 +1755,7 @@ fn resolve_fn_call_column( file, name_ref, &table_name, - &schema, + schema.as_ref(), &column_name, ) { @@ -1782,23 +1857,10 @@ fn find_column_in_create_table_impl( ast_nav::CreateTableArg::Inherits(path) => { let (schema, table_name) = name::schema_and_name_path(&path)?; let position = path.syntax().text_range().start(); - if let Some(resolved) = resolve_table_name(db, file, &table_name, &schema, position) - && let Some(ptr) = match resolved { - ResolvedTableName::Table(parent_table) => find_column_in_create_table_impl( - db, - file, - &parent_table, - column_name, - depth + 1, - ), - ResolvedTableName::TableAs(create_table_as) => { - find_column_in_create_table_as(file, &create_table_as, column_name) - } - ResolvedTableName::SelectInto(select_into) => { - find_column_in_select_into(file, &select_into, column_name) - } - ResolvedTableName::View(_) => None, - } + if let Some((lookup_file, resolved)) = + resolve_table_name(db, file, &table_name, schema.as_ref(), position) + && let Some(ptr) = + find_column_in_resolved(db, lookup_file, resolved, column_name, depth + 1) { return Some(ptr); } @@ -1818,23 +1880,10 @@ fn find_column_in_create_table_impl( let path = like_clause.path()?; let (schema, table_name) = name::schema_and_name_path(&path)?; let position = path.syntax().text_range().start(); - if let Some(resolved) = resolve_table_name(db, file, &table_name, &schema, position) - && let Some(ptr) = match resolved { - ResolvedTableName::Table(source_table) => find_column_in_create_table_impl( - db, - file, - &source_table, - column_name, - depth + 1, - ), - ResolvedTableName::TableAs(create_table_as) => { - find_column_in_create_table_as(file, &create_table_as, column_name) - } - ResolvedTableName::SelectInto(select_into) => { - find_column_in_select_into(file, &select_into, column_name) - } - ResolvedTableName::View(_) => None, - } + if let Some((lookup_file, resolved)) = + resolve_table_name(db, file, &table_name, schema.as_ref(), position) + && let Some(ptr) = + find_column_in_resolved(db, lookup_file, resolved, column_name, depth + 1) { return Some(ptr); } @@ -1846,6 +1895,29 @@ fn find_column_in_create_table_impl( None } +fn find_column_in_resolved( + db: &dyn Db, + file: File, + resolved: ResolvedTableName, + column_name: &Name, + depth: usize, +) -> Option> { + match resolved { + ResolvedTableName::Table(parent_table) => { + find_column_in_create_table_impl(db, file, &parent_table, column_name, depth) + } + ResolvedTableName::TableAs(create_table_as) => { + find_column_in_create_table_as(db, file, &create_table_as, column_name) + } + ResolvedTableName::SelectInto(select_into) => { + find_column_in_select_into(file, &select_into, column_name) + } + ResolvedTableName::View(create_view) => { + find_column_in_create_view_like(file, &create_view, column_name) + } + } +} + // TODO: this is similar to the CTE funcs, maybe we can simplify fn find_column_in_create_view_like( file: File, @@ -1893,29 +1965,86 @@ fn find_column_in_create_view_like( } fn find_column_in_create_table_as( + db: &dyn Db, file: File, create_table_as: &ast::CreateTableAs, column_name: &Name, ) -> Option> { - let select = ast_nav::select_from_variant(create_table_as.query()?)?; + let query = create_table_as.query()?; - for target in select.select_clause()?.target_list()?.targets() { - if let Some((col_name, node)) = ColumnName::from_target(target.clone()) { - if let Some(col_name_str) = col_name.to_string() - && Name::from_string(col_name_str) == *column_name - { + if let ast::SelectVariant::Values(values) = &query { + for (col_name, expr) in ast_nav::iter_values_columns(values) { + if col_name == *column_name { return Some(smallvec![Location::new( file, - node.text_range(), + expr.syntax().text_range(), LocationKind::Column )]); } } + return None; + } + + if let ast::SelectVariant::Table(table) = &query { + let path = table.relation_name()?.path()?; + let (schema, table_name) = name::schema_and_name_path(&path)?; + let position = table.syntax().text_range().start(); + let (lookup_file, resolved) = + resolve_table_name(db, file, &table_name, schema.as_ref(), position)?; + return find_column_in_resolved(db, lookup_file, resolved, column_name, 0); + } + + let select = ast_nav::select_from_variant(query)?; + let target_list = select.select_clause()?.target_list()?; + let from_clause = select.from_clause(); + + for target in target_list.targets() { + if target.star_token().is_some() { + if let Some(from_clause) = &from_clause + && let Some(result) = find_column_in_from_clause(db, file, from_clause, column_name) + { + return Some(result); + } + continue; + } + if let Some((col_name, node)) = ColumnName::from_target(target.clone()) + && let Some(col_name_str) = col_name.to_string() + && Name::from_string(col_name_str) == *column_name + { + return Some(smallvec![Location::new( + file, + node.text_range(), + LocationKind::Column + )]); + } } None } +fn find_column_in_from_clause( + db: &dyn Db, + file: File, + from_clause: &ast::FromClause, + column_name: &Name, +) -> Option> { + for from_item in ast_nav::iter_from_clause(from_clause) { + let Some((schema, table_name)) = name::schema_and_table_from_from_item(&from_item) else { + continue; + }; + let position = from_item.syntax().text_range().start(); + let Some((lookup_file, resolved)) = + resolve_table_name(db, file, &table_name, schema.as_ref(), position) + else { + continue; + }; + if let Some(result) = find_column_in_resolved(db, lookup_file, resolved, column_name, 0) { + return Some(result); + } + } + None +} + fn find_column_in_select_into( file: File, select_into: &ast::SelectInto, @@ -1945,14 +2074,14 @@ fn resolve_cte_table(name_ref: &ast::NameRef, cte_name: &Name) -> Option Option { let (schema, table_name) = name::schema_and_name_path(path)?; let position = path.syntax().text_range().start(); - count_columns_for_table_name(db, file, &table_name, &schema, position, None) + count_columns_for_table_name(db, file, &table_name, schema.as_ref(), position, None) } fn count_columns_for_table_name( db: &dyn Db, file: File, table_name: &Name, - schema: &Option, + schema: Option<&Schema>, position: TextSize, name_ref: Option<&ast::NameRef>, ) -> Option { @@ -2105,7 +2234,7 @@ fn resolve_cte_column( file, name_ref, &table_name, - &schema, + schema.as_ref(), column_name, ); } @@ -2289,7 +2418,7 @@ fn resolve_subquery_column_ptr( file, name_ref, &table_name, - &schema, + schema.as_ref(), column_name, ); } @@ -2413,9 +2542,14 @@ pub(crate) fn table_ptrs_from_clause( let name_ref = from_item.name_ref(); let position = from_item.syntax().text_range().start(); - if let Some((table_like_ptr, _kind)) = - resolve_table_like(db, file, name_ref.as_ref(), &table_name, &schema, position) - { + if let Some((table_like_ptr, _kind)) = resolve_table_like( + db, + file, + name_ref.as_ref(), + &table_name, + schema.as_ref(), + position, + ) { results.push(table_like_ptr); } } @@ -2440,8 +2574,15 @@ pub(crate) fn table_ptr_from_from_item( let name_ref = from_item.name_ref(); let position = from_item.syntax().text_range().start(); - resolve_table_like(db, file, name_ref.as_ref(), &table_name, &schema, position) - .map(|(table_like_ptr, _kind)| table_like_ptr) + resolve_table_like( + db, + file, + name_ref.as_ref(), + &table_name, + schema.as_ref(), + position, + ) + .map(|(table_like_ptr, _kind)| table_like_ptr) } fn table_ptr_from_paren_expr( @@ -2506,7 +2647,14 @@ fn count_columns_for_from_item( ) -> Option { let (schema, table_name) = name::schema_and_table_from_from_item(from_item)?; let position = name_ref.syntax().text_range().start(); - count_columns_for_table_name(db, file, &table_name, &schema, position, Some(name_ref)) + count_columns_for_table_name( + db, + file, + &table_name, + schema.as_ref(), + position, + Some(name_ref), + ) } fn resolve_from_clause_for_cte_star( @@ -2615,7 +2763,8 @@ fn resolve_column_from_call_expr_return_table( ) -> Option> { let position = name_ref.syntax().text_range().start(); let (schema, function_name) = name::schema_and_func_name(call_expr)?; - let function_locs = resolve_function(db, file, &function_name, &schema, None, position)?; + let function_locs = + resolve_function(db, file, &function_name, schema.as_ref(), None, position)?; let function_node = function_locs.first()?.to_node(db)?; let create_function = function_node .ancestors() @@ -2764,7 +2913,7 @@ fn resolve_composite_type_field_ptr( .or_else(|| resolve_composite_type_from_cast_node(&column_node))?; let position = field_name_ref.syntax().text_range().start(); - let type_name_ptr = resolve_type_name_ptr(db, file, &type_name, &schema, position)?; + let type_name_ptr = resolve_type_name_ptr(db, file, &type_name, schema.as_ref(), position)?; let type_node = type_name_ptr.to_node(root); let create_type = type_node.ancestors().find_map(ast::CreateType::cast)?; @@ -2854,7 +3003,8 @@ fn resolve_table_in_returning_clause( LocationKind::Table )]), ReturningClauseMatch::PseudoTable => { - let ptr = resolve_table_name_ptr(db, file, &stmt_table_name, &schema, position)?; + let ptr = + resolve_table_name_ptr(db, file, &stmt_table_name, schema.as_ref(), position)?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -2862,7 +3012,7 @@ fn resolve_table_in_returning_clause( )]) } ReturningClauseMatch::Table => { - let ptr = resolve_table_name_ptr(db, file, &table_name, &schema, position)?; + let ptr = resolve_table_name_ptr(db, file, &table_name, schema.as_ref(), position)?; Some(smallvec![Location::new( file, ptr.text_range(), @@ -2891,14 +3041,8 @@ fn resolve_merge_table_name_ptr( let item_name = Name::from_node(&item_name_ref); if item_name == table_name { let position = table_name_ref.syntax().text_range().start(); - let (ptr, kind) = resolve_table_like( - db, - file, - Some(table_name_ref), - &item_name, - &None, - position, - )?; + let (ptr, kind) = + resolve_table_like(db, file, Some(table_name_ref), &item_name, None, position)?; return Some(smallvec![Location::new(file, ptr.text_range(), kind)]); } }