diff --git a/crates/squawk_ide/src/code_actions/rewrite_overlaps_as_function_call.rs b/crates/squawk_ide/src/code_actions/rewrite_overlaps_as_function_call.rs index baaf8ffc..9a245494 100644 --- a/crates/squawk_ide/src/code_actions/rewrite_overlaps_as_function_call.rs +++ b/crates/squawk_ide/src/code_actions/rewrite_overlaps_as_function_call.rs @@ -76,7 +76,9 @@ fn tuple_ranges(tuple: &SyntaxNode) -> Option { mod test { use insta::assert_snapshot; - use crate::code_actions::test_utils::{apply_code_action, code_action_not_applicable}; + use crate::code_actions::test_utils::{ + apply_code_action, code_action_not_applicable, code_action_not_applicable_with_errors, + }; use super::rewrite_overlaps_as_function_call; @@ -126,7 +128,7 @@ mod test { #[test] fn not_applicable_to_non_pair_tuples() { - assert!(code_action_not_applicable( + assert!(code_action_not_applicable_with_errors( rewrite_overlaps_as_function_call, "select (a, b, c) over$0laps (d, e);" )); diff --git a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__empty_in_expr_validation.snap b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__empty_in_expr_validation.snap new file mode 100644 index 00000000..d51f21bf --- /dev/null +++ b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__empty_in_expr_validation.snap @@ -0,0 +1,79 @@ +--- +source: crates/squawk_syntax/src/test.rs +input_file: crates/squawk_syntax/test_data/validation/empty_in_expr.sql +--- +SOURCE_FILE@0..66 + SELECT@0..30 + SELECT_CLAUSE@0..8 + SELECT_KW@0..6 "SELECT" + WHITESPACE@6..7 " " + TARGET_LIST@7..8 + TARGET@7..8 + STAR@7..8 "*" + WHITESPACE@8..9 " " + FROM_CLAUSE@9..15 + FROM_KW@9..13 "FROM" + WHITESPACE@13..14 " " + RELATION_FROM_ITEM@14..15 + RELATION_NAME_REF@14..15 + PATH_REF@14..15 + PATH_SEGMENT_REF@14..15 + IDENT@14..15 "t" + WHITESPACE@15..16 " " + WHERE_CLAUSE@16..29 + WHERE_KW@16..21 "WHERE" + WHITESPACE@21..22 " " + BIN_EXPR@22..29 + NAME_REF@22..23 + IDENT@22..23 "a" + WHITESPACE@23..24 " " + IN_KW@24..26 "IN" + WHITESPACE@26..27 " " + TUPLE_EXPR@27..29 + L_PAREN@27..28 "(" + R_PAREN@28..29 ")" + SEMICOLON@29..30 ";" + WHITESPACE@30..31 "\n" + SELECT@31..65 + SELECT_CLAUSE@31..39 + SELECT_KW@31..37 "SELECT" + WHITESPACE@37..38 " " + TARGET_LIST@38..39 + TARGET@38..39 + STAR@38..39 "*" + WHITESPACE@39..40 " " + FROM_CLAUSE@40..46 + FROM_KW@40..44 "FROM" + WHITESPACE@44..45 " " + RELATION_FROM_ITEM@45..46 + RELATION_NAME_REF@45..46 + PATH_REF@45..46 + PATH_SEGMENT_REF@45..46 + IDENT@45..46 "t" + WHITESPACE@46..47 " " + WHERE_CLAUSE@47..64 + WHERE_KW@47..52 "WHERE" + WHITESPACE@52..53 " " + BIN_EXPR@53..64 + NAME_REF@53..54 + IDENT@53..54 "a" + WHITESPACE@54..55 " " + NOT_IN@55..61 + NOT_KW@55..58 "NOT" + WHITESPACE@58..59 " " + IN_KW@59..61 "IN" + WHITESPACE@61..62 " " + TUPLE_EXPR@62..64 + L_PAREN@62..63 "(" + R_PAREN@63..64 ")" + SEMICOLON@64..65 ";" + WHITESPACE@65..66 "\n" + +error[syntax-error]: Expected at least one expression in IN list. + ╭▸ +1 │ SELECT * FROM t WHERE a IN (); + ╰╴ ━━ +error[syntax-error]: Expected at least one expression in IN list. + ╭▸ +2 │ SELECT * FROM t WHERE a NOT IN (); + ╰╴ ━━ diff --git a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__overlaps_validation.snap b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__overlaps_validation.snap new file mode 100644 index 00000000..21474f40 --- /dev/null +++ b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__overlaps_validation.snap @@ -0,0 +1,112 @@ +--- +source: crates/squawk_syntax/src/test.rs +input_file: crates/squawk_syntax/test_data/validation/overlaps.sql +--- +SOURCE_FILE@0..286 + COMMENT@0..61 "-- OVERLAPS is only d ..." + WHITESPACE@61..62 "\n" + SELECT@62..82 + SELECT_CLAUSE@62..81 + SELECT_KW@62..68 "SELECT" + WHITESPACE@68..69 " " + TARGET_LIST@69..81 + TARGET@69..81 + BIN_EXPR@69..81 + LITERAL@69..70 + INT_NUMBER@69..70 "1" + WHITESPACE@70..71 " " + OVERLAPS_KW@71..79 "OVERLAPS" + WHITESPACE@79..80 " " + LITERAL@80..81 + INT_NUMBER@80..81 "2" + SEMICOLON@81..82 ";" + WHITESPACE@82..84 "\n\n" + COMMENT@84..149 "-- wrong number of pa ..." + WHITESPACE@149..150 "\n" + SELECT@150..183 + SELECT_CLAUSE@150..182 + SELECT_KW@150..156 "SELECT" + WHITESPACE@156..157 " " + TARGET_LIST@157..182 + TARGET@157..182 + BIN_EXPR@157..182 + TUPLE_EXPR@157..166 + L_PAREN@157..158 "(" + LITERAL@158..159 + INT_NUMBER@158..159 "1" + COMMA@159..160 "," + WHITESPACE@160..161 " " + LITERAL@161..162 + INT_NUMBER@161..162 "2" + COMMA@162..163 "," + WHITESPACE@163..164 " " + LITERAL@164..165 + INT_NUMBER@164..165 "3" + R_PAREN@165..166 ")" + WHITESPACE@166..167 " " + OVERLAPS_KW@167..175 "OVERLAPS" + WHITESPACE@175..176 " " + TUPLE_EXPR@176..182 + L_PAREN@176..177 "(" + LITERAL@177..178 + INT_NUMBER@177..178 "1" + COMMA@178..179 "," + WHITESPACE@179..180 " " + LITERAL@180..181 + INT_NUMBER@180..181 "2" + R_PAREN@181..182 ")" + SEMICOLON@182..183 ";" + WHITESPACE@183..185 "\n\n" + COMMENT@185..251 "-- wrong number of pa ..." + WHITESPACE@251..252 "\n" + SELECT@252..285 + SELECT_CLAUSE@252..284 + SELECT_KW@252..258 "SELECT" + WHITESPACE@258..259 " " + TARGET_LIST@259..284 + TARGET@259..284 + BIN_EXPR@259..284 + TUPLE_EXPR@259..265 + L_PAREN@259..260 "(" + LITERAL@260..261 + INT_NUMBER@260..261 "1" + COMMA@261..262 "," + WHITESPACE@262..263 " " + LITERAL@263..264 + INT_NUMBER@263..264 "2" + R_PAREN@264..265 ")" + WHITESPACE@265..266 " " + OVERLAPS_KW@266..274 "OVERLAPS" + WHITESPACE@274..275 " " + TUPLE_EXPR@275..284 + L_PAREN@275..276 "(" + LITERAL@276..277 + INT_NUMBER@276..277 "1" + COMMA@277..278 "," + WHITESPACE@278..279 " " + LITERAL@279..280 + INT_NUMBER@279..280 "2" + COMMA@280..281 "," + WHITESPACE@281..282 " " + LITERAL@282..283 + INT_NUMBER@282..283 "3" + R_PAREN@283..284 ")" + SEMICOLON@284..285 ";" + WHITESPACE@285..286 "\n" + +error[syntax-error]: OVERLAPS operand must be a row expression + ╭▸ +2 │ SELECT 1 OVERLAPS 2; + ╰╴ ━ +error[syntax-error]: OVERLAPS operand must be a row expression + ╭▸ +2 │ SELECT 1 OVERLAPS 2; + ╰╴ ━ +error[syntax-error]: wrong number of parameters + ╭▸ +5 │ SELECT (1, 2, 3) OVERLAPS (1, 2); + ╰╴ ━━━━━━━━━ +error[syntax-error]: wrong number of parameters + ╭▸ +8 │ SELECT (1, 2) OVERLAPS (1, 2, 3); + ╰╴ ━━━━━━━━━ diff --git a/crates/squawk_syntax/src/validation.rs b/crates/squawk_syntax/src/validation.rs index 5c0ee5ed..ad26adcc 100644 --- a/crates/squawk_syntax/src/validation.rs +++ b/crates/squawk_syntax/src/validation.rs @@ -590,17 +590,27 @@ fn offset_range(start: TextSize, range: Range) -> TextRange { TextRange::new(begin, end) } -// see: https://christopher.xyz/2020/08/01/any-all-pg.html fn validate_bin_expr(bin_expr: ast::BinExpr, acc: &mut Vec) { - if !matches!( - bin_expr.op(), - Some(ast::BinOp::In(_) | ast::BinOp::NotIn(_)) - ) { - return; + match bin_expr.op() { + Some(ast::BinOp::In(_) | ast::BinOp::NotIn(_)) => validate_in_expr(&bin_expr, acc), + Some(ast::BinOp::Overlaps(_)) => validate_overlaps_expr(&bin_expr, acc), + _ => (), } +} + +// see: https://christopher.xyz/2020/08/01/any-all-pg.html +fn validate_in_expr(bin_expr: &ast::BinExpr, acc: &mut Vec) { let rhs = match bin_expr.rhs() { None | Some(ast::Expr::ParenExpr(_)) => return, - Some(ast::Expr::TupleExpr(tuple)) if tuple.row_token().is_none() => return, + Some(ast::Expr::TupleExpr(tuple)) if tuple.row_token().is_none() => { + if tuple.exprs().next().is_none() { + acc.push(SyntaxError::new( + "Expected at least one expression in IN list.", + tuple.syntax().text_range(), + )); + } + return; + } Some(rhs) => rhs, }; acc.push(SyntaxError::new( @@ -609,6 +619,21 @@ fn validate_bin_expr(bin_expr: ast::BinExpr, acc: &mut Vec) { )); } +fn validate_overlaps_expr(bin_expr: &ast::BinExpr, acc: &mut Vec) { + for operand in [bin_expr.lhs(), bin_expr.rhs()].into_iter().flatten() { + match operand { + ast::Expr::TupleExpr(tuple) if tuple.exprs().count() != 2 => acc.push( + SyntaxError::new("wrong number of parameters", tuple.syntax().text_range()), + ), + ast::Expr::TupleExpr(_) => (), + operand => acc.push(SyntaxError::new( + "OVERLAPS operand must be a row expression", + operand.syntax().text_range(), + )), + } + } +} + fn validate_join_expr(join_expr: ast::JoinExpr, acc: &mut Vec) { let Some(join) = join_expr.join() else { return; diff --git a/crates/squawk_syntax/test_data/validation/empty_in_expr.sql b/crates/squawk_syntax/test_data/validation/empty_in_expr.sql new file mode 100644 index 00000000..efe9660f --- /dev/null +++ b/crates/squawk_syntax/test_data/validation/empty_in_expr.sql @@ -0,0 +1,2 @@ +SELECT * FROM t WHERE a IN (); +SELECT * FROM t WHERE a NOT IN (); diff --git a/crates/squawk_syntax/test_data/validation/overlaps.sql b/crates/squawk_syntax/test_data/validation/overlaps.sql new file mode 100644 index 00000000..ab9121c4 --- /dev/null +++ b/crates/squawk_syntax/test_data/validation/overlaps.sql @@ -0,0 +1,8 @@ +-- OVERLAPS is only defined over row expressions, not scalars +SELECT 1 OVERLAPS 2; + +-- wrong number of parameters on left side of OVERLAPS expression +SELECT (1, 2, 3) OVERLAPS (1, 2); + +-- wrong number of parameters on right side of OVERLAPS expression +SELECT (1, 2) OVERLAPS (1, 2, 3);