Skip to content

Commit 9724678

Browse files
authored
1 parent ec8dcd0 commit 9724678

File tree

1 file changed

+290
-0
lines changed

1 file changed

+290
-0
lines changed

crates/squawk_ide/src/code_actions.rs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ pub fn code_actions(file: ast::SourceFile, offset: TextSize) -> Option<Vec<CodeA
2323
rewrite_as_regular_string(&mut actions, &file, offset);
2424
rewrite_as_dollar_quoted_string(&mut actions, &file, offset);
2525
remove_else_clause(&mut actions, &file, offset);
26+
rewrite_table_as_select(&mut actions, &file, offset);
27+
rewrite_select_as_table(&mut actions, &file, offset);
2628
Some(actions)
2729
}
2830

@@ -155,6 +157,142 @@ fn remove_else_clause(
155157
Some(())
156158
}
157159

160+
fn rewrite_table_as_select(
161+
actions: &mut Vec<CodeAction>,
162+
file: &ast::SourceFile,
163+
offset: TextSize,
164+
) -> Option<()> {
165+
let node = file.syntax().token_at_offset(offset).left_biased()?;
166+
let table = node.parent_ancestors().find_map(ast::Table::cast)?;
167+
168+
let relation_name = table.relation_name()?;
169+
let table_name = relation_name.syntax().text();
170+
171+
let replacement = format!("select * from {}", table_name);
172+
173+
actions.push(CodeAction {
174+
title: "Rewrite as `select`".to_owned(),
175+
edits: vec![Edit::replace(table.syntax().text_range(), replacement)],
176+
kind: ActionKind::RefactorRewrite,
177+
});
178+
179+
Some(())
180+
}
181+
182+
fn rewrite_select_as_table(
183+
actions: &mut Vec<CodeAction>,
184+
file: &ast::SourceFile,
185+
offset: TextSize,
186+
) -> Option<()> {
187+
let node = file.syntax().token_at_offset(offset).left_biased()?;
188+
let select = node.parent_ancestors().find_map(ast::Select::cast)?;
189+
190+
if !can_transform_select_to_table(&select) {
191+
return None;
192+
}
193+
194+
let from_clause = select.from_clause()?;
195+
let from_item = from_clause.from_items().next()?;
196+
197+
let table_name = if let Some(name_ref) = from_item.name_ref() {
198+
name_ref.syntax().text().to_string()
199+
} else if let Some(field_expr) = from_item.field_expr() {
200+
field_expr.syntax().text().to_string()
201+
} else {
202+
return None;
203+
};
204+
205+
let replacement = format!("table {}", table_name);
206+
207+
actions.push(CodeAction {
208+
title: "Rewrite as `table`".to_owned(),
209+
edits: vec![Edit::replace(select.syntax().text_range(), replacement)],
210+
kind: ActionKind::RefactorRewrite,
211+
});
212+
213+
Some(())
214+
}
215+
216+
/// Returns true if a `select` statement can be safely rewritten as a `table` statement.
217+
///
218+
/// We can only do this when there are no clauses besides the `select` and
219+
/// `from` clause. Additionally, we can only have a table reference in the
220+
/// `from` clause.
221+
/// The `select`'s target list must only be a `*`.
222+
fn can_transform_select_to_table(select: &ast::Select) -> bool {
223+
if select.with_clause().is_some()
224+
|| select.where_clause().is_some()
225+
|| select.group_by_clause().is_some()
226+
|| select.having_clause().is_some()
227+
|| select.window_clause().is_some()
228+
|| select.order_by_clause().is_some()
229+
|| select.limit_clause().is_some()
230+
|| select.fetch_clause().is_some()
231+
|| select.offset_clause().is_some()
232+
|| select.filter_clause().is_some()
233+
|| select.locking_clauses().next().is_some()
234+
{
235+
return false;
236+
}
237+
238+
let Some(select_clause) = select.select_clause() else {
239+
return false;
240+
};
241+
242+
if select_clause.distinct_clause().is_some() {
243+
return false;
244+
}
245+
246+
let Some(target_list) = select_clause.target_list() else {
247+
return false;
248+
};
249+
250+
let mut targets = target_list.targets();
251+
let Some(target) = targets.next() else {
252+
return false;
253+
};
254+
255+
if targets.next().is_some() {
256+
return false;
257+
}
258+
259+
// only want to support: `select *`
260+
if target.expr().is_some() || target.star_token().is_none() {
261+
return false;
262+
}
263+
264+
let Some(from_clause) = select.from_clause() else {
265+
return false;
266+
};
267+
268+
let mut from_items = from_clause.from_items();
269+
let Some(from_item) = from_items.next() else {
270+
return false;
271+
};
272+
273+
// only can have one from item & no join exprs
274+
if from_items.next().is_some() || from_clause.join_exprs().next().is_some() {
275+
return false;
276+
}
277+
278+
if from_item.alias().is_some()
279+
|| from_item.tablesample_clause().is_some()
280+
|| from_item.only_token().is_some()
281+
|| from_item.lateral_token().is_some()
282+
|| from_item.star_token().is_some()
283+
|| from_item.call_expr().is_some()
284+
|| from_item.paren_select().is_some()
285+
|| from_item.json_table().is_some()
286+
|| from_item.xml_table().is_some()
287+
|| from_item.cast_expr().is_some()
288+
{
289+
return false;
290+
}
291+
292+
// only want table refs
293+
from_item.name_ref().is_some() || from_item.field_expr().is_some()
294+
}
295+
158296
#[cfg(test)]
159297
mod test {
160298
use super::*;
@@ -349,4 +487,156 @@ mod test {
349487
"select 'foo$0';"
350488
));
351489
}
490+
491+
#[test]
492+
fn rewrite_table_as_select_simple() {
493+
assert_snapshot!(apply_code_action(
494+
rewrite_table_as_select,
495+
"tab$0le foo;"),
496+
@"select * from foo;"
497+
);
498+
}
499+
500+
#[test]
501+
fn rewrite_table_as_select_qualified() {
502+
assert_snapshot!(apply_code_action(
503+
rewrite_table_as_select,
504+
"ta$0ble schema.foo;"),
505+
@"select * from schema.foo;"
506+
);
507+
}
508+
509+
#[test]
510+
fn rewrite_table_as_select_after_keyword() {
511+
assert_snapshot!(apply_code_action(
512+
rewrite_table_as_select,
513+
"table$0 bar;"),
514+
@"select * from bar;"
515+
);
516+
}
517+
518+
#[test]
519+
fn rewrite_table_as_select_on_table_name() {
520+
assert_snapshot!(apply_code_action(
521+
rewrite_table_as_select,
522+
"table fo$0o;"),
523+
@"select * from foo;"
524+
);
525+
}
526+
527+
#[test]
528+
fn rewrite_table_as_select_not_applicable() {
529+
assert!(code_action_not_applicable(
530+
rewrite_table_as_select,
531+
"select * from foo$0;"
532+
));
533+
}
534+
535+
#[test]
536+
fn rewrite_select_as_table_simple() {
537+
assert_snapshot!(apply_code_action(
538+
rewrite_select_as_table,
539+
"sel$0ect * from foo;"),
540+
@"table foo;"
541+
);
542+
}
543+
544+
#[test]
545+
fn rewrite_select_as_table_qualified() {
546+
assert_snapshot!(apply_code_action(
547+
rewrite_select_as_table,
548+
"select * from sch$0ema.foo;"),
549+
@"table schema.foo;"
550+
);
551+
}
552+
553+
#[test]
554+
fn rewrite_select_as_table_on_star() {
555+
assert_snapshot!(apply_code_action(
556+
rewrite_select_as_table,
557+
"select $0* from bar;"),
558+
@"table bar;"
559+
);
560+
}
561+
562+
#[test]
563+
fn rewrite_select_as_table_on_from() {
564+
assert_snapshot!(apply_code_action(
565+
rewrite_select_as_table,
566+
"select * fr$0om baz;"),
567+
@"table baz;"
568+
);
569+
}
570+
571+
#[test]
572+
fn rewrite_select_as_table_not_applicable_with_where() {
573+
assert!(code_action_not_applicable(
574+
rewrite_select_as_table,
575+
"select * from foo$0 where x = 1;"
576+
));
577+
}
578+
579+
#[test]
580+
fn rewrite_select_as_table_not_applicable_with_order_by() {
581+
assert!(code_action_not_applicable(
582+
rewrite_select_as_table,
583+
"select * from foo$0 order by x;"
584+
));
585+
}
586+
587+
#[test]
588+
fn rewrite_select_as_table_not_applicable_with_limit() {
589+
assert!(code_action_not_applicable(
590+
rewrite_select_as_table,
591+
"select * from foo$0 limit 10;"
592+
));
593+
}
594+
595+
#[test]
596+
fn rewrite_select_as_table_not_applicable_with_distinct() {
597+
assert!(code_action_not_applicable(
598+
rewrite_select_as_table,
599+
"select distinct * from foo$0;"
600+
));
601+
}
602+
603+
#[test]
604+
fn rewrite_select_as_table_not_applicable_with_columns() {
605+
assert!(code_action_not_applicable(
606+
rewrite_select_as_table,
607+
"select id, name from foo$0;"
608+
));
609+
}
610+
611+
#[test]
612+
fn rewrite_select_as_table_not_applicable_with_join() {
613+
assert!(code_action_not_applicable(
614+
rewrite_select_as_table,
615+
"select * from foo$0 join bar on foo.id = bar.id;"
616+
));
617+
}
618+
619+
#[test]
620+
fn rewrite_select_as_table_not_applicable_with_alias() {
621+
assert!(code_action_not_applicable(
622+
rewrite_select_as_table,
623+
"select * from foo$0 f;"
624+
));
625+
}
626+
627+
#[test]
628+
fn rewrite_select_as_table_not_applicable_with_multiple_tables() {
629+
assert!(code_action_not_applicable(
630+
rewrite_select_as_table,
631+
"select * from foo$0, bar;"
632+
));
633+
}
634+
635+
#[test]
636+
fn rewrite_select_as_table_not_applicable_on_table() {
637+
assert!(code_action_not_applicable(
638+
rewrite_select_as_table,
639+
"table foo$0;"
640+
));
641+
}
352642
}

0 commit comments

Comments
 (0)