Skip to content

Commit 49ea2dd

Browse files
authored
ide: hover for create aggregate (#789)
1 parent accede9 commit 49ea2dd

File tree

2 files changed

+192
-1
lines changed

2 files changed

+192
-1
lines changed

PLAN.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ FROM (
590590
WHERE total_amount > 1000;
591591
```
592592

593-
### Rule: aggregate free having condition
593+
### Rule: aggregate free `having` condition
594594

595595
```sql
596596
select a from t group by a having a > 10;
@@ -600,6 +600,21 @@ select a from t group by a having a > 10;
600600
select a from t where a > 10 group by a;
601601
```
602602

603+
### Rule: conflicting function and aggregate definitions
604+
605+
```sql
606+
create function foo(int) returns int as $$
607+
select $1 * 2;
608+
$$ language sql;
609+
610+
create aggregate foo(int) (
611+
sfunc = int4pl,
612+
stype = int,
613+
initcond = '0'
614+
);
615+
-- Query 1 ERROR at Line 1: : ERROR: function "foo" already exists with same argument types
616+
```
617+
603618
### Rule: order direction is redundent
604619

605620
```sql

crates/squawk_ide/src/hover.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option<String> {
4444
return hover_function(file, &name_ref, &binder);
4545
}
4646

47+
if is_aggregate_ref(&name_ref) {
48+
return hover_aggregate(file, &name_ref, &binder);
49+
}
50+
4751
if is_select_function_call(&name_ref) {
4852
// Try function first, but fall back to column if no function found
4953
// (handles function-call-style column access like `select a(t)`)
@@ -85,6 +89,14 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option<String> {
8589
return format_create_function(&create_function, &binder);
8690
}
8791

92+
if let Some(create_aggregate) = name
93+
.syntax()
94+
.ancestors()
95+
.find_map(ast::CreateAggregate::cast)
96+
{
97+
return format_create_aggregate(&create_aggregate, &binder);
98+
}
99+
88100
if let Some(create_schema) = name.syntax().ancestors().find_map(ast::CreateSchema::cast) {
89101
return format_create_schema(&create_schema);
90102
}
@@ -353,6 +365,15 @@ fn is_function_ref(name_ref: &ast::NameRef) -> bool {
353365
false
354366
}
355367

368+
fn is_aggregate_ref(name_ref: &ast::NameRef) -> bool {
369+
for ancestor in name_ref.syntax().ancestors() {
370+
if ast::DropAggregate::can_cast(ancestor.kind()) {
371+
return true;
372+
}
373+
}
374+
false
375+
}
376+
356377
fn is_select_function_call(name_ref: &ast::NameRef) -> bool {
357378
let mut in_call_expr = false;
358379
let mut in_arg_list = false;
@@ -498,6 +519,53 @@ fn function_schema(
498519
search_path.first().map(|s| s.to_string())
499520
}
500521

522+
fn hover_aggregate(
523+
file: &ast::SourceFile,
524+
name_ref: &ast::NameRef,
525+
binder: &binder::Binder,
526+
) -> Option<String> {
527+
let aggregate_ptr = resolve::resolve_name_ref(binder, name_ref)?;
528+
529+
let root = file.syntax();
530+
let aggregate_name_node = aggregate_ptr.to_node(root);
531+
532+
let create_aggregate = aggregate_name_node
533+
.ancestors()
534+
.find_map(ast::CreateAggregate::cast)?;
535+
536+
format_create_aggregate(&create_aggregate, binder)
537+
}
538+
539+
fn format_create_aggregate(
540+
create_aggregate: &ast::CreateAggregate,
541+
binder: &binder::Binder,
542+
) -> Option<String> {
543+
let path = create_aggregate.path()?;
544+
let segment = path.segment()?;
545+
let name = segment.name()?;
546+
let aggregate_name = name.syntax().text().to_string();
547+
548+
let schema = if let Some(qualifier) = path.qualifier() {
549+
qualifier.syntax().text().to_string()
550+
} else {
551+
aggregate_schema(create_aggregate, binder)?
552+
};
553+
554+
let param_list = create_aggregate.param_list()?;
555+
let params = param_list.syntax().text().to_string();
556+
557+
Some(format!("aggregate {}.{}{}", schema, aggregate_name, params))
558+
}
559+
560+
fn aggregate_schema(
561+
create_aggregate: &ast::CreateAggregate,
562+
binder: &binder::Binder,
563+
) -> Option<String> {
564+
let position = create_aggregate.syntax().text_range().start();
565+
let search_path = binder.search_path_at(position);
566+
search_path.first().map(|s| s.to_string())
567+
}
568+
501569
#[cfg(test)]
502570
mod test {
503571
use crate::hover::hover;
@@ -1003,6 +1071,114 @@ drop function foo$0();
10031071
");
10041072
}
10051073

1074+
#[test]
1075+
fn hover_on_drop_function_overloaded() {
1076+
assert_snapshot!(check_hover("
1077+
create function add(complex) returns complex as $$ select null $$ language sql;
1078+
create function add(bigint) returns bigint as $$ select 1 $$ language sql;
1079+
drop function add$0(complex);
1080+
"), @r"
1081+
hover: function public.add(complex) returns complex
1082+
╭▸
1083+
4 │ drop function add(complex);
1084+
╰╴ ─ hover
1085+
");
1086+
}
1087+
1088+
#[test]
1089+
fn hover_on_drop_function_second_overload() {
1090+
assert_snapshot!(check_hover("
1091+
create function add(complex) returns complex as $$ select null $$ language sql;
1092+
create function add(bigint) returns bigint as $$ select 1 $$ language sql;
1093+
drop function add$0(bigint);
1094+
"), @r"
1095+
hover: function public.add(bigint) returns bigint
1096+
╭▸
1097+
4 │ drop function add(bigint);
1098+
╰╴ ─ hover
1099+
");
1100+
}
1101+
1102+
#[test]
1103+
fn hover_on_drop_aggregate() {
1104+
assert_snapshot!(check_hover("
1105+
create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);
1106+
drop aggregate myavg$0(int);
1107+
"), @r"
1108+
hover: aggregate public.myavg(int)
1109+
╭▸
1110+
3 │ drop aggregate myavg(int);
1111+
╰╴ ─ hover
1112+
");
1113+
}
1114+
1115+
#[test]
1116+
fn hover_on_drop_aggregate_with_schema() {
1117+
assert_snapshot!(check_hover("
1118+
create aggregate myschema.myavg(int) (sfunc = int4_avg_accum, stype = _int8);
1119+
drop aggregate myschema.myavg$0(int);
1120+
"), @r"
1121+
hover: aggregate myschema.myavg(int)
1122+
╭▸
1123+
3 │ drop aggregate myschema.myavg(int);
1124+
╰╴ ─ hover
1125+
");
1126+
}
1127+
1128+
#[test]
1129+
fn hover_on_create_aggregate_definition() {
1130+
assert_snapshot!(check_hover("
1131+
create aggregate myavg$0(int) (sfunc = int4_avg_accum, stype = _int8);
1132+
"), @r"
1133+
hover: aggregate public.myavg(int)
1134+
╭▸
1135+
2 │ create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);
1136+
╰╴ ─ hover
1137+
");
1138+
}
1139+
1140+
#[test]
1141+
fn hover_on_drop_aggregate_with_search_path() {
1142+
assert_snapshot!(check_hover(r#"
1143+
set search_path to myschema;
1144+
create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);
1145+
drop aggregate myavg$0(int);
1146+
"#), @r"
1147+
hover: aggregate myschema.myavg(int)
1148+
╭▸
1149+
4 │ drop aggregate myavg(int);
1150+
╰╴ ─ hover
1151+
");
1152+
}
1153+
1154+
#[test]
1155+
fn hover_on_drop_aggregate_overloaded() {
1156+
assert_snapshot!(check_hover("
1157+
create aggregate sum(complex) (sfunc = complex_add, stype = complex, initcond = '(0,0)');
1158+
create aggregate sum(bigint) (sfunc = bigint_add, stype = bigint, initcond = '0');
1159+
drop aggregate sum$0(complex);
1160+
"), @r"
1161+
hover: aggregate public.sum(complex)
1162+
╭▸
1163+
4 │ drop aggregate sum(complex);
1164+
╰╴ ─ hover
1165+
");
1166+
}
1167+
1168+
#[test]
1169+
fn hover_on_drop_aggregate_second_overload() {
1170+
assert_snapshot!(check_hover("
1171+
create aggregate sum(complex) (sfunc = complex_add, stype = complex, initcond = '(0,0)');
1172+
create aggregate sum(bigint) (sfunc = bigint_add, stype = bigint, initcond = '0');
1173+
drop aggregate sum$0(bigint);
1174+
"), @r"
1175+
hover: aggregate public.sum(bigint)
1176+
╭▸
1177+
4 │ drop aggregate sum(bigint);
1178+
╰╴ ─ hover
1179+
");
1180+
}
1181+
10061182
#[test]
10071183
fn hover_on_select_function_call() {
10081184
assert_snapshot!(check_hover("

0 commit comments

Comments
 (0)