diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index 49eb0959..5e7a0710 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -12909,6 +12909,19 @@ fn copy_option_list(p: &mut Parser<'_>) { m.complete(p, COPY_OPTION_LIST); } +fn opt_copy_force_target(p: &mut Parser<'_>) -> bool { + if p.eat(STAR) { + return true; + } + if name_ref(p).is_none() { + return false; + } + while !p.at(EOF) && p.eat(COMMA) { + name_ref(p); + } + true +} + fn opt_copy_option_item(p: &mut Parser<'_>) -> bool { let m = p.start(); let parsed = match p.current() { @@ -12933,19 +12946,20 @@ fn opt_copy_option_item(p: &mut Parser<'_>) -> bool { NOT_KW => { p.bump_any(); p.expect(NULL_KW); - if !p.eat(STAR) { - name_ref_list(p); - } - true + opt_copy_force_target(p) } - QUOTE_KW | NULL_KW => { + NULL_KW => { p.bump_any(); - if !p.eat(STAR) { - name_ref_list(p); - } - true + opt_copy_force_target(p) + } + QUOTE_KW => { + p.bump_any(); + opt_copy_force_target(p) + } + _ => { + p.error("expected NOT, QUOTE, or NULL"); + false } - _ => false, } } _ => false, diff --git a/crates/squawk_parser/tests/data/err/copy.sql b/crates/squawk_parser/tests/data/err/copy.sql index bef9ad68..c18ed700 100644 --- a/crates/squawk_parser/tests/data/err/copy.sql +++ b/crates/squawk_parser/tests/data/err/copy.sql @@ -1,2 +1,7 @@ -- missing a couple commas copy x (i y) from '/tmp/input.file' (on_error ignore log_verbosity verbose); + +-- legacy force option requires a target +copy t from 's' with force; +copy t from 's' with force null; +copy t from 's' with force zzz; diff --git a/crates/squawk_parser/tests/snapshots/tests__copy_err.snap b/crates/squawk_parser/tests/snapshots/tests__copy_err.snap index d2940655..45726f2d 100644 --- a/crates/squawk_parser/tests/snapshots/tests__copy_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__copy_err.snap @@ -44,6 +44,68 @@ SOURCE_FILE VERBOSE_KW "verbose" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- legacy force option requires a target" + WHITESPACE "\n" + COPY + COPY_KW "copy" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " " + FROM_KW "from" + WHITESPACE " " + LITERAL + STRING "'s'" + WHITESPACE " " + WITH_KW "with" + WHITESPACE " " + FORCE_KW "force" + SEMICOLON ";" + WHITESPACE "\n" + COPY + COPY_KW "copy" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " " + FROM_KW "from" + WHITESPACE " " + LITERAL + STRING "'s'" + WHITESPACE " " + WITH_KW "with" + WHITESPACE " " + FORCE_KW "force" + WHITESPACE " " + NULL_KW "null" + SEMICOLON ";" + WHITESPACE "\n" + COPY + COPY_KW "copy" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " " + FROM_KW "from" + WHITESPACE " " + LITERAL + STRING "'s'" + WHITESPACE " " + WITH_KW "with" + WHITESPACE " " + FORCE_KW "force" + WHITESPACE " " + ERROR + IDENT "zzz" + EMPTY_STMT + SEMICOLON ";" WHITESPACE "\n" --- error[syntax-error]: expected COMMA @@ -54,3 +116,19 @@ error[syntax-error]: expected COMMA ╭▸ 2 │ copy x (i y) from '/tmp/input.file' (on_error ignore log_verbosity verbose); ╰╴ ━ +error[syntax-error]: expected NOT, QUOTE, or NULL + ╭▸ +5 │ copy t from 's' with force; + ╰╴ ━ +error[syntax-error]: expected name + ╭▸ +6 │ copy t from 's' with force null; + ╰╴ ━ +error[syntax-error]: expected NOT, QUOTE, or NULL + ╭▸ +7 │ copy t from 's' with force zzz; + ╰╴ ━ +error[syntax-error]: expected command, found IDENT + ╭▸ +7 │ copy t from 's' with force zzz; + ╰╴ ━