@@ -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) ]
159297mod 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