diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2749969c0..099f0bb7b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11081,12 +11081,13 @@ impl<'a> Parser<'a> { while let Some(opt) = self.maybe_parse(|parser| parser.parse_copy_legacy_option())? { legacy_options.push(opt); } - let values = if let CopyTarget::Stdin = target { - self.expect_token(&Token::SemiColon)?; - self.parse_tsv() - } else { - vec![] - }; + let values = + if matches!(target, CopyTarget::Stdin) && self.peek_token_ref().token != Token::EOF { + self.expect_token(&Token::SemiColon)?; + self.parse_tsv() + } else { + vec![] + }; Ok(Statement::Copy { source, to, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 7dd624a27..fdb9bcf61 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1123,6 +1123,62 @@ PHP ₱ USD $ pg_and_generic().one_statement_parses_to(sql, ""); } +#[test] +fn parse_copy_from_stdin_without_semicolon() { + let stmt = pg().verified_stmt("COPY bitwise_test FROM STDIN NULL 'null'"); + assert_eq!( + stmt, + Statement::Copy { + source: CopySource::Table { + table_name: ObjectName::from(vec!["bitwise_test".into()]), + columns: vec![], + }, + to: false, + target: CopyTarget::Stdin, + options: vec![], + legacy_options: vec![CopyLegacyOption::Null("null".into())], + values: vec![], + } + ); +} + +#[test] +fn parse_copy_from_stdin_without_semicolon_variants() { + // This covers additional COPY ... FROM STDIN shapes without inline payload. + // `parse_copy_from_stdin_without_semicolon` asserts the legacy NULL option details. + let cases = [ + "COPY varbit_table FROM STDIN", + "COPY bit_table FROM STDIN", + "COPY copytest2 (test) FROM STDIN", + "COPY copytest3 FROM STDIN CSV HEADER", + "COPY copytest4 FROM STDIN (HEADER)", + "COPY parted_copytest FROM STDIN", + "COPY tab_progress_reporting FROM STDIN", + "COPY oversized_column_default FROM STDIN", + "COPY x (a, b, c, d, e) FROM STDIN", + "COPY header_copytest (c, a) FROM STDIN", + "COPY atest5 (two) FROM STDIN", + "COPY main_table (a, b) FROM STDIN", + ]; + + for sql in cases { + match pg().verified_stmt(sql) { + Statement::Copy { + to: false, + target: CopyTarget::Stdin, + values, + .. + } => { + assert!( + values.is_empty(), + "expected no inline COPY payload for `{sql}`" + ); + } + _ => panic!("expected COPY ... FROM STDIN statement for `{sql}`"), + } + } +} + #[test] fn test_copy_from() { let stmt = pg().verified_stmt("COPY users FROM 'data.csv'");