From 4eb80d17748700fda84a5455dfb400db62a1d8d4 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Wed, 20 May 2026 20:16:44 -0400 Subject: [PATCH 01/10] junk lexing --- crates/squawk_lexer/src/lib.rs | 34 ++++++++++++ .../squawk_lexer__tests__lex_statement.snap | 2 +- .../squawk_lexer__tests__numeric.snap | 22 ++++---- ...awk_lexer__tests__numeric_non_decimal.snap | 12 ++--- ...lexer__tests__numeric_with_seperators.snap | 10 ++-- crates/squawk_lexer/src/token.rs | 20 ++++++- crates/squawk_parser/src/lexed_str.rs | 11 +++- .../tests__regression_numerology.snap | 54 ++++++++++++++++++- crates/squawk_parser/tests/tests.rs | 4 +- crates/squawk_syntax/src/test.rs | 4 +- 10 files changed, 142 insertions(+), 31 deletions(-) diff --git a/crates/squawk_lexer/src/lib.rs b/crates/squawk_lexer/src/lib.rs index 8f92513da..9d9faaecf 100644 --- a/crates/squawk_lexer/src/lib.rs +++ b/crates/squawk_lexer/src/lib.rs @@ -231,6 +231,33 @@ impl Cursor<'_> { } fn number(&mut self, first_digit: char) -> LiteralKind { + let kind = self.number_content(first_digit); + let trailing_junk_start = self.pos_within_token(); + if is_ident_start(self.first()) { + self.eat_while(is_ident_cont); + } + match kind { + LiteralKind::Int { + base, empty_int, .. + } => LiteralKind::Int { + base, + empty_int, + trailing_junk_start, + }, + LiteralKind::Float { + base, + empty_exponent, + .. + } => LiteralKind::Float { + base, + empty_exponent, + trailing_junk_start, + }, + _ => unreachable!(), + } + } + + fn number_content(&mut self, first_digit: char) -> LiteralKind { let mut base = Base::Decimal; if first_digit == '0' { // Attempt to parse encoding base. @@ -243,6 +270,7 @@ impl Cursor<'_> { return LiteralKind::Int { base, empty_int: true, + trailing_junk_start: 0, }; } } @@ -254,6 +282,7 @@ impl Cursor<'_> { return LiteralKind::Int { base, empty_int: true, + trailing_junk_start: 0, }; } } @@ -265,6 +294,7 @@ impl Cursor<'_> { return LiteralKind::Int { base, empty_int: true, + trailing_junk_start: 0, }; } } @@ -281,6 +311,7 @@ impl Cursor<'_> { return LiteralKind::Int { base, empty_int: false, + trailing_junk_start: 0, }; } } @@ -316,6 +347,7 @@ impl Cursor<'_> { LiteralKind::Float { base, empty_exponent, + trailing_junk_start: 0, } } 'e' | 'E' => { @@ -324,11 +356,13 @@ impl Cursor<'_> { LiteralKind::Float { base, empty_exponent, + trailing_junk_start: 0, } } _ => LiteralKind::Int { base, empty_int: false, + trailing_junk_start: 0, }, } } diff --git a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__lex_statement.snap b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__lex_statement.snap index a76c42ba9..aabe9de6e 100644 --- a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__lex_statement.snap +++ b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__lex_statement.snap @@ -5,6 +5,6 @@ expression: result [ "select" @ Ident, " " @ Whitespace, - "1" @ Literal { kind: Int { base: Decimal, empty_int: false } }, + "1" @ Literal { kind: Int { base: Decimal, empty_int: false, trailing_junk_start: 1 } }, ";" @ Semi, ] diff --git a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric.snap b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric.snap index 519cf3d94..addf6bb52 100644 --- a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric.snap +++ b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric.snap @@ -4,26 +4,26 @@ expression: "lex(r#\"\n42\n3.5\n4.\n.001\n.123e10\n5e2\n1.925e-3\n1e-10\n1e+10\n --- [ "\n" @ Whitespace, - "42" @ Literal { kind: Int { base: Decimal, empty_int: false } }, + "42" @ Literal { kind: Int { base: Decimal, empty_int: false, trailing_junk_start: 2 } }, "\n" @ Whitespace, - "3.5" @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + "3.5" @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 3 } }, "\n" @ Whitespace, - "4." @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + "4." @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 2 } }, "\n" @ Whitespace, - ".001" @ Literal { kind: Int { base: Decimal, empty_int: false } }, + ".001" @ Literal { kind: Int { base: Decimal, empty_int: false, trailing_junk_start: 4 } }, "\n" @ Whitespace, - ".123e10" @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + ".123e10" @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 7 } }, "\n" @ Whitespace, - "5e2" @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + "5e2" @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 3 } }, "\n" @ Whitespace, - "1.925e-3" @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + "1.925e-3" @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 8 } }, "\n" @ Whitespace, - "1e-10" @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + "1e-10" @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 5 } }, "\n" @ Whitespace, - "1e+10" @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + "1e+10" @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 5 } }, "\n" @ Whitespace, - "1e10" @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + "1e10" @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 4 } }, "\n" @ Whitespace, - "4664.E+5" @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + "4664.E+5" @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 8 } }, "\n" @ Whitespace, ] diff --git a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric_non_decimal.snap b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric_non_decimal.snap index 5050265f5..6ec55318c 100644 --- a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric_non_decimal.snap +++ b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric_non_decimal.snap @@ -4,16 +4,16 @@ expression: "lex(r#\"\n0b100101\n0B10011001\n0o273\n0O755\n0x42f\n0XFFFF\n\"#)" --- [ "\n" @ Whitespace, - "0b100101" @ Literal { kind: Int { base: Binary, empty_int: false } }, + "0b100101" @ Literal { kind: Int { base: Binary, empty_int: false, trailing_junk_start: 8 } }, "\n" @ Whitespace, - "0B10011001" @ Literal { kind: Int { base: Binary, empty_int: false } }, + "0B10011001" @ Literal { kind: Int { base: Binary, empty_int: false, trailing_junk_start: 10 } }, "\n" @ Whitespace, - "0o273" @ Literal { kind: Int { base: Octal, empty_int: false } }, + "0o273" @ Literal { kind: Int { base: Octal, empty_int: false, trailing_junk_start: 5 } }, "\n" @ Whitespace, - "0O755" @ Literal { kind: Int { base: Octal, empty_int: false } }, + "0O755" @ Literal { kind: Int { base: Octal, empty_int: false, trailing_junk_start: 5 } }, "\n" @ Whitespace, - "0x42f" @ Literal { kind: Int { base: Hexadecimal, empty_int: false } }, + "0x42f" @ Literal { kind: Int { base: Hexadecimal, empty_int: false, trailing_junk_start: 5 } }, "\n" @ Whitespace, - "0XFFFF" @ Literal { kind: Int { base: Hexadecimal, empty_int: false } }, + "0XFFFF" @ Literal { kind: Int { base: Hexadecimal, empty_int: false, trailing_junk_start: 6 } }, "\n" @ Whitespace, ] diff --git a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric_with_seperators.snap b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric_with_seperators.snap index 46814c5f6..bfc095b89 100644 --- a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric_with_seperators.snap +++ b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__numeric_with_seperators.snap @@ -4,14 +4,14 @@ expression: "lex(r#\"\n1_500_000_000\n0b10001000_00000000\n0o_1_755\n0xFFFF_FFFF --- [ "\n" @ Whitespace, - "1_500_000_000" @ Literal { kind: Int { base: Decimal, empty_int: false } }, + "1_500_000_000" @ Literal { kind: Int { base: Decimal, empty_int: false, trailing_junk_start: 13 } }, "\n" @ Whitespace, - "0b10001000_00000000" @ Literal { kind: Int { base: Binary, empty_int: false } }, + "0b10001000_00000000" @ Literal { kind: Int { base: Binary, empty_int: false, trailing_junk_start: 19 } }, "\n" @ Whitespace, - "0o_1_755" @ Literal { kind: Int { base: Octal, empty_int: false } }, + "0o_1_755" @ Literal { kind: Int { base: Octal, empty_int: false, trailing_junk_start: 8 } }, "\n" @ Whitespace, - "0xFFFF_FFFF" @ Literal { kind: Int { base: Hexadecimal, empty_int: false } }, + "0xFFFF_FFFF" @ Literal { kind: Int { base: Hexadecimal, empty_int: false, trailing_junk_start: 11 } }, "\n" @ Whitespace, - "1.618_034" @ Literal { kind: Float { base: Decimal, empty_exponent: false } }, + "1.618_034" @ Literal { kind: Float { base: Decimal, empty_exponent: false, trailing_junk_start: 9 } }, "\n" @ Whitespace, ] diff --git a/crates/squawk_lexer/src/token.rs b/crates/squawk_lexer/src/token.rs index 525e84a0d..4d471ccbb 100644 --- a/crates/squawk_lexer/src/token.rs +++ b/crates/squawk_lexer/src/token.rs @@ -126,12 +126,28 @@ pub enum Base { pub enum LiteralKind { /// Integer Numeric, e.g., `42` /// + /// `trailing_junk_start` is the byte offset within the token where the + /// numeric content ends. When it is less than the token length, the + /// remainder is an invalid ident-like suffix (e.g. `foo` in `1000foo`). + /// /// see: - Int { base: Base, empty_int: bool }, + Int { + base: Base, + empty_int: bool, + trailing_junk_start: u32, + }, /// Float Numeric, e.g., `1.925e-3` /// + /// `trailing_junk_start` is the byte offset within the token where the + /// numeric content ends. When it is less than the token length, the + /// remainder is an invalid ident-like suffix (e.g. `foo` in `1.5foo`). + /// /// see: - Float { base: Base, empty_exponent: bool }, + Float { + base: Base, + empty_exponent: bool, + trailing_junk_start: u32, + }, /// String, e.g., `'foo'` /// /// see: diff --git a/crates/squawk_parser/src/lexed_str.rs b/crates/squawk_parser/src/lexed_str.rs index 8d377622c..394a25557 100644 --- a/crates/squawk_parser/src/lexed_str.rs +++ b/crates/squawk_parser/src/lexed_str.rs @@ -227,18 +227,27 @@ impl<'a> Converter<'a> { let mut err: Option = None; let syntax_kind = match *kind { - squawk_lexer::LiteralKind::Int { empty_int, base: _ } => { + squawk_lexer::LiteralKind::Int { + empty_int, + base: _, + trailing_junk_start, + } => { if empty_int { err = Some("Missing digits after the integer base prefix".into()); + } else if (trailing_junk_start as usize) < token_text.len() { + err = Some("trailing junk after numeric literal".into()); } SyntaxKind::INT_NUMBER } squawk_lexer::LiteralKind::Float { empty_exponent, base: _, + trailing_junk_start, } => { if empty_exponent { err = Some("Missing digits after the exponent symbol".into()); + } else if (trailing_junk_start as usize) < token_text.len() { + err = Some("trailing junk after numeric literal".into()); } SyntaxKind::FLOAT_NUMBER } diff --git a/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap b/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap index 5a3a21e39..cd2a5f364 100644 --- a/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap +++ b/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap @@ -2,4 +2,56 @@ source: crates/squawk_parser/tests/tests.rs input_file: postgres/regression_suite/numerology.sql --- - +--- +error[syntax-error]: trailing junk after numeric literal + ╭▸ +46 │ SELECT 123abc; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +47 │ SELECT 0x0o; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +48 │ SELECT 0.a; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +49 │ SELECT 0.0a; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +50 │ SELECT .0a; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +51 │ SELECT 0.0e1a; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +58 │ SELECT 1b; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +59 │ SELECT 0b0x; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +62 │ SELECT 1o; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +63 │ SELECT 0o0x; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +66 │ SELECT 1x; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +67 │ SELECT 0x0y; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +97 │ SELECT 1_000._5; + ╰╴ ━ diff --git a/crates/squawk_parser/tests/tests.rs b/crates/squawk_parser/tests/tests.rs index 0f62cb09a..068234932 100644 --- a/crates/squawk_parser/tests/tests.rs +++ b/crates/squawk_parser/tests/tests.rs @@ -82,7 +82,7 @@ fn regression_suite(fixture: Fixture<&str>) { let (_parsed, errors) = parse_text(content); - let expect_errors = test_name == "errors"; + let expect_errors = matches!(test_name, "errors" | "numerology"); let snapshot_name = format!("regression_{test_name}"); @@ -99,7 +99,7 @@ fn regression_suite(fixture: Fixture<&str>) { if expect_errors { assert!( has_errors, - "the errors.sql regression test must have errors" + "the {test_name}.sql regression test must have errors" ); } else { assert!( diff --git a/crates/squawk_syntax/src/test.rs b/crates/squawk_syntax/src/test.rs index 3d600ce67..012101876 100644 --- a/crates/squawk_syntax/src/test.rs +++ b/crates/squawk_syntax/src/test.rs @@ -77,10 +77,10 @@ fn regression_suite_validation(fixture: Fixture<&str>) { let mut errors = errors; - if test_name == "errors" { + if matches!(test_name, "errors" | "numerology") { assert!( !errors.is_empty(), - "the errors.sql regression test must have validation errors" + "the {test_name}.sql regression test must have validation errors" ); return; } From b447e68e0266aa54df69f7c40e447cd8c42b78e5 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Wed, 20 May 2026 22:00:37 -0400 Subject: [PATCH 02/10] refactor --- crates/squawk_lexer/src/lib.rs | 71 ++++++++++++++++------------------ 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/crates/squawk_lexer/src/lib.rs b/crates/squawk_lexer/src/lib.rs index 9d9faaecf..8763d5031 100644 --- a/crates/squawk_lexer/src/lib.rs +++ b/crates/squawk_lexer/src/lib.rs @@ -231,33 +231,6 @@ impl Cursor<'_> { } fn number(&mut self, first_digit: char) -> LiteralKind { - let kind = self.number_content(first_digit); - let trailing_junk_start = self.pos_within_token(); - if is_ident_start(self.first()) { - self.eat_while(is_ident_cont); - } - match kind { - LiteralKind::Int { - base, empty_int, .. - } => LiteralKind::Int { - base, - empty_int, - trailing_junk_start, - }, - LiteralKind::Float { - base, - empty_exponent, - .. - } => LiteralKind::Float { - base, - empty_exponent, - trailing_junk_start, - }, - _ => unreachable!(), - } - } - - fn number_content(&mut self, first_digit: char) -> LiteralKind { let mut base = Base::Decimal; if first_digit == '0' { // Attempt to parse encoding base. @@ -267,10 +240,12 @@ impl Cursor<'_> { base = Base::Binary; self.bump(); if !self.eat_decimal_digits() { + let trailing_junk_start = self.pos_within_token(); + self.eat_identifier(); return LiteralKind::Int { base, empty_int: true, - trailing_junk_start: 0, + trailing_junk_start, }; } } @@ -279,10 +254,12 @@ impl Cursor<'_> { base = Base::Octal; self.bump(); if !self.eat_decimal_digits() { + let trailing_junk_start = self.pos_within_token(); + self.eat_identifier(); return LiteralKind::Int { base, empty_int: true, - trailing_junk_start: 0, + trailing_junk_start, }; } } @@ -291,10 +268,12 @@ impl Cursor<'_> { base = Base::Hexadecimal; self.bump(); if !self.eat_hexadecimal_digits() { + let trailing_junk_start = self.pos_within_token(); + self.eat_identifier(); return LiteralKind::Int { base, empty_int: true, - trailing_junk_start: 0, + trailing_junk_start, }; } } @@ -308,10 +287,12 @@ impl Cursor<'_> { // Just a 0. _ => { + let trailing_junk_start = self.pos_within_token(); + self.eat_identifier(); return LiteralKind::Int { base, empty_int: false, - trailing_junk_start: 0, + trailing_junk_start, }; } } @@ -344,26 +325,34 @@ impl Cursor<'_> { _ => (), } } + let trailing_junk_start = self.pos_within_token(); + self.eat_identifier(); LiteralKind::Float { base, empty_exponent, - trailing_junk_start: 0, + trailing_junk_start, } } 'e' | 'E' => { self.bump(); let empty_exponent = !self.eat_float_exponent(); + let trailing_junk_start = self.pos_within_token(); + self.eat_identifier(); LiteralKind::Float { base, empty_exponent, - trailing_junk_start: 0, + trailing_junk_start, + } + } + _ => { + let trailing_junk_start = self.pos_within_token(); + self.eat_identifier(); + LiteralKind::Int { + base, + empty_int: false, + trailing_junk_start, } } - _ => LiteralKind::Int { - base, - empty_int: false, - trailing_junk_start: 0, - }, } } @@ -524,6 +513,12 @@ impl Cursor<'_> { } self.eat_decimal_digits() } + + fn eat_identifier(&mut self) { + if is_ident_start(self.first()) { + self.eat_while(is_ident_cont); + } + } } /// Creates an iterator that produces tokens from the input string. From e4388ff7eb01e9ff1812bd77752198a2c5fc2ea1 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Wed, 20 May 2026 22:01:31 -0400 Subject: [PATCH 03/10] undo --- crates/squawk_lexer/src/token.rs | 8 -------- crates/squawk_parser/tests/tests.rs | 2 +- crates/squawk_syntax/src/test.rs | 4 ++-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/crates/squawk_lexer/src/token.rs b/crates/squawk_lexer/src/token.rs index 4d471ccbb..ace606b85 100644 --- a/crates/squawk_lexer/src/token.rs +++ b/crates/squawk_lexer/src/token.rs @@ -126,10 +126,6 @@ pub enum Base { pub enum LiteralKind { /// Integer Numeric, e.g., `42` /// - /// `trailing_junk_start` is the byte offset within the token where the - /// numeric content ends. When it is less than the token length, the - /// remainder is an invalid ident-like suffix (e.g. `foo` in `1000foo`). - /// /// see: Int { base: Base, @@ -138,10 +134,6 @@ pub enum LiteralKind { }, /// Float Numeric, e.g., `1.925e-3` /// - /// `trailing_junk_start` is the byte offset within the token where the - /// numeric content ends. When it is less than the token length, the - /// remainder is an invalid ident-like suffix (e.g. `foo` in `1.5foo`). - /// /// see: Float { base: Base, diff --git a/crates/squawk_parser/tests/tests.rs b/crates/squawk_parser/tests/tests.rs index 068234932..8a200b3f4 100644 --- a/crates/squawk_parser/tests/tests.rs +++ b/crates/squawk_parser/tests/tests.rs @@ -82,7 +82,7 @@ fn regression_suite(fixture: Fixture<&str>) { let (_parsed, errors) = parse_text(content); - let expect_errors = matches!(test_name, "errors" | "numerology"); + let expect_errors = test_name == "errors"; let snapshot_name = format!("regression_{test_name}"); diff --git a/crates/squawk_syntax/src/test.rs b/crates/squawk_syntax/src/test.rs index 012101876..3d600ce67 100644 --- a/crates/squawk_syntax/src/test.rs +++ b/crates/squawk_syntax/src/test.rs @@ -77,10 +77,10 @@ fn regression_suite_validation(fixture: Fixture<&str>) { let mut errors = errors; - if matches!(test_name, "errors" | "numerology") { + if test_name == "errors" { assert!( !errors.is_empty(), - "the {test_name}.sql regression test must have validation errors" + "the errors.sql regression test must have validation errors" ); return; } From e7435db209e620df880991d517a090b26284d210 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Wed, 20 May 2026 22:04:05 -0400 Subject: [PATCH 04/10] update --- crates/squawk_parser/tests/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/squawk_parser/tests/tests.rs b/crates/squawk_parser/tests/tests.rs index 8a200b3f4..0f62cb09a 100644 --- a/crates/squawk_parser/tests/tests.rs +++ b/crates/squawk_parser/tests/tests.rs @@ -99,7 +99,7 @@ fn regression_suite(fixture: Fixture<&str>) { if expect_errors { assert!( has_errors, - "the {test_name}.sql regression test must have errors" + "the errors.sql regression test must have errors" ); } else { assert!( From b6431ce638d7647942875c4613163d9b09b7e3ec Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Wed, 20 May 2026 22:20:27 -0400 Subject: [PATCH 05/10] add test --- .../tests/data/err/select_literal.sql | 26 ++ .../snapshots/tests__select_literal_err.snap | 351 ++++++++++++++++++ 2 files changed, 377 insertions(+) create mode 100644 crates/squawk_parser/tests/data/err/select_literal.sql create mode 100644 crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap diff --git a/crates/squawk_parser/tests/data/err/select_literal.sql b/crates/squawk_parser/tests/data/err/select_literal.sql new file mode 100644 index 000000000..79b2f82ab --- /dev/null +++ b/crates/squawk_parser/tests/data/err/select_literal.sql @@ -0,0 +1,26 @@ +SELECT 123abc; +SELECT 0x0o; +SELECT 0.a; +SELECT 0.0a; +SELECT .0a; +SELECT 0.0e1a; +SELECT 0.0e; +SELECT 0.0e+a; +SELECT $1a; +SELECT 0b; +SELECT 1b; +SELECT 0b0x; +SELECT 0o; +SELECT 1o; +SELECT 0o0x; +SELECT 0x; +SELECT 1x; +SELECT 0x0y; +SELECT 100_; +SELECT 100__000; +SELECT _1_000.5; +SELECT 1_000_.5; +SELECT 1_000._5; +SELECT 1_000.5_; +SELECT 1_000.5e_1; +SELECT $0_1; diff --git a/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap b/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap new file mode 100644 index 000000000..81c76051f --- /dev/null +++ b/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap @@ -0,0 +1,351 @@ +--- +source: crates/squawk_parser/tests/tests.rs +input_file: crates/squawk_parser/tests/data/err/select_literal.sql +--- +SOURCE_FILE + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "123abc" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "0x0o" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + FLOAT_NUMBER "0.a" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + FLOAT_NUMBER "0.0a" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER ".0a" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + FLOAT_NUMBER "0.0e1a" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + FLOAT_NUMBER "0.0e" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + FLOAT_NUMBER "0.0e+a" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + POSITIONAL_PARAM "$1" + AS_NAME + NAME + IDENT "a" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "0b" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1b" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "0b0x" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "0o" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1o" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "0o0x" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "0x" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1x" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "0x0y" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "100_" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "100__000" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + NAME_REF + IDENT "_1_000" + TARGET + LITERAL + INT_NUMBER ".5" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + FLOAT_NUMBER "1_000_.5" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + FLOAT_NUMBER "1_000._5" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + FLOAT_NUMBER "1_000.5_" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + FLOAT_NUMBER "1_000.5e_1" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + POSITIONAL_PARAM "$0" + AS_NAME + NAME + IDENT "_1" + SEMICOLON ";" + WHITESPACE "\n" +--- +error[syntax-error]: trailing junk after numeric literal + ╭▸ +1 │ SELECT 123abc; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +2 │ SELECT 0x0o; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +3 │ SELECT 0.a; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +4 │ SELECT 0.0a; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +5 │ SELECT .0a; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +6 │ SELECT 0.0e1a; + ╰╴ ━ +error[syntax-error]: Missing digits after the exponent symbol + ╭▸ +7 │ SELECT 0.0e; + ╰╴ ━ +error[syntax-error]: Missing digits after the exponent symbol + ╭▸ +8 │ SELECT 0.0e+a; + ╰╴ ━ +error[syntax-error]: Missing digits after the integer base prefix + ╭▸ +10 │ SELECT 0b; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +11 │ SELECT 1b; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +12 │ SELECT 0b0x; + ╰╴ ━ +error[syntax-error]: Missing digits after the integer base prefix + ╭▸ +13 │ SELECT 0o; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +14 │ SELECT 1o; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +15 │ SELECT 0o0x; + ╰╴ ━ +error[syntax-error]: Missing digits after the integer base prefix + ╭▸ +16 │ SELECT 0x; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +17 │ SELECT 1x; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +18 │ SELECT 0x0y; + ╰╴ ━ +error[syntax-error]: missing comma + ╭▸ +21 │ SELECT _1_000.5; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +23 │ SELECT 1_000._5; + ╰╴ ━ From 6a794bb5f804fafc34b93aa8e9ada76b33189576 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Wed, 20 May 2026 22:40:24 -0400 Subject: [PATCH 06/10] trailing junk positional param --- crates/squawk_lexer/src/lib.rs | 6 +++++- .../snapshots/squawk_lexer__tests__params.snap | 8 ++++---- crates/squawk_lexer/src/token.rs | 2 +- crates/squawk_parser/src/lexed_str.rs | 9 ++++++++- .../tests__regression_numerology.snap | 8 ++++++++ .../snapshots/tests__select_literal_err.snap | 18 ++++++++++-------- 6 files changed, 36 insertions(+), 15 deletions(-) diff --git a/crates/squawk_lexer/src/lib.rs b/crates/squawk_lexer/src/lib.rs index 8763d5031..efbfab394 100644 --- a/crates/squawk_lexer/src/lib.rs +++ b/crates/squawk_lexer/src/lib.rs @@ -116,7 +116,11 @@ impl Cursor<'_> { while self.first().is_ascii_digit() { self.bump(); } - TokenKind::PositionalParam + let trailing_junk_start = self.pos_within_token(); + self.eat_identifier(); + TokenKind::PositionalParam { + trailing_junk_start, + } } } '`' => TokenKind::Backtick, diff --git a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__params.snap b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__params.snap index 8ee456f48..2a5dd0afa 100644 --- a/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__params.snap +++ b/crates/squawk_lexer/src/snapshots/squawk_lexer__tests__params.snap @@ -6,21 +6,21 @@ expression: "lex(r#\"\nselect $1 + $2;\n\nselect $1123123123123;\n\nselect $;\n\ "\n" @ Whitespace, "select" @ Ident, " " @ Whitespace, - "$1" @ PositionalParam, + "$1" @ PositionalParam { trailing_junk_start: 2 }, " " @ Whitespace, "+" @ Plus, " " @ Whitespace, - "$2" @ PositionalParam, + "$2" @ PositionalParam { trailing_junk_start: 2 }, ";" @ Semi, "\n\n" @ Whitespace, "select" @ Ident, " " @ Whitespace, - "$1123123123123" @ PositionalParam, + "$1123123123123" @ PositionalParam { trailing_junk_start: 14 }, ";" @ Semi, "\n\n" @ Whitespace, "select" @ Ident, " " @ Whitespace, - "$" @ PositionalParam, + "$" @ PositionalParam { trailing_junk_start: 1 }, ";" @ Semi, "\n" @ Whitespace, ] diff --git a/crates/squawk_lexer/src/token.rs b/crates/squawk_lexer/src/token.rs index ace606b85..bec1b5b13 100644 --- a/crates/squawk_lexer/src/token.rs +++ b/crates/squawk_lexer/src/token.rs @@ -84,7 +84,7 @@ pub enum TokenKind { /// Positional Parameter, e.g., `$1` /// /// see: - PositionalParam, + PositionalParam { trailing_junk_start: u32 }, /// Quoted Identifier, e.g., `"update"` in `update "my_table" set "a" = 5;` /// /// These are case-sensitive, unlike [`TokenKind::Ident`] diff --git a/crates/squawk_parser/src/lexed_str.rs b/crates/squawk_parser/src/lexed_str.rs index 394a25557..e2bced591 100644 --- a/crates/squawk_parser/src/lexed_str.rs +++ b/crates/squawk_parser/src/lexed_str.rs @@ -209,7 +209,14 @@ impl<'a> Converter<'a> { } squawk_lexer::TokenKind::Eof => SyntaxKind::EOF, squawk_lexer::TokenKind::Backtick => SyntaxKind::BACKTICK, - squawk_lexer::TokenKind::PositionalParam => SyntaxKind::POSITIONAL_PARAM, + squawk_lexer::TokenKind::PositionalParam { + trailing_junk_start, + } => { + if (*trailing_junk_start as usize) < token_text.len() { + err = "trailing junk after positional parameter"; + } + SyntaxKind::POSITIONAL_PARAM + } squawk_lexer::TokenKind::QuotedIdent { terminated } => { if !terminated { err = "Missing trailing \" to terminate the quoted identifier" diff --git a/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap b/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap index cd2a5f364..da2d90b00 100644 --- a/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap +++ b/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap @@ -27,6 +27,10 @@ error[syntax-error]: trailing junk after numeric literal ╭▸ 51 │ SELECT 0.0e1a; ╰╴ ━ +error[syntax-error]: trailing junk after positional parameter + ╭▸ +54 │ PREPARE p1 AS SELECT $1a; + ╰╴ ━ error[syntax-error]: trailing junk after numeric literal ╭▸ 58 │ SELECT 1b; @@ -55,3 +59,7 @@ error[syntax-error]: trailing junk after numeric literal ╭▸ 97 │ SELECT 1_000._5; ╰╴ ━ +error[syntax-error]: trailing junk after positional parameter + ╭▸ +101 │ PREPARE p1 AS SELECT $0_1; + ╰╴ ━ diff --git a/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap b/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap index 81c76051f..e53941401 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap @@ -90,10 +90,7 @@ SOURCE_FILE TARGET_LIST TARGET LITERAL - POSITIONAL_PARAM "$1" - AS_NAME - NAME - IDENT "a" + POSITIONAL_PARAM "$1a" SEMICOLON ";" WHITESPACE "\n" SELECT @@ -266,10 +263,7 @@ SOURCE_FILE TARGET_LIST TARGET LITERAL - POSITIONAL_PARAM "$0" - AS_NAME - NAME - IDENT "_1" + POSITIONAL_PARAM "$0_1" SEMICOLON ";" WHITESPACE "\n" --- @@ -305,6 +299,10 @@ error[syntax-error]: Missing digits after the exponent symbol ╭▸ 8 │ SELECT 0.0e+a; ╰╴ ━ +error[syntax-error]: trailing junk after positional parameter + ╭▸ +9 │ SELECT $1a; + ╰╴ ━ error[syntax-error]: Missing digits after the integer base prefix ╭▸ 10 │ SELECT 0b; @@ -349,3 +347,7 @@ error[syntax-error]: trailing junk after numeric literal ╭▸ 23 │ SELECT 1_000._5; ╰╴ ━ +error[syntax-error]: trailing junk after positional parameter + ╭▸ +26 │ SELECT $0_1; + ╰╴ ━ From a5cf98978b140ab3e56bee4ea553f7bcd0363fb3 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Thu, 21 May 2026 00:22:01 -0400 Subject: [PATCH 07/10] fix --- crates/squawk_lexer/src/lib.rs | 7 +++-- .../snapshots/tests__select_literal_err.snap | 29 ++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/crates/squawk_lexer/src/lib.rs b/crates/squawk_lexer/src/lib.rs index efbfab394..9725f88f0 100644 --- a/crates/squawk_lexer/src/lib.rs +++ b/crates/squawk_lexer/src/lib.rs @@ -479,7 +479,7 @@ impl Cursor<'_> { let mut has_digits = false; loop { match self.first() { - '_' => { + '_' if matches!(self.second(), '0'..='9') => { self.bump(); } '0'..='9' => { @@ -496,7 +496,7 @@ impl Cursor<'_> { let mut has_digits = false; loop { match self.first() { - '_' => { + '_' if matches!(self.second(), '0'..='9' | 'a'..='f' | 'A'..='F') => { self.bump(); } '0'..='9' | 'a'..='f' | 'A'..='F' => { @@ -512,6 +512,9 @@ impl Cursor<'_> { /// Eats the float exponent. Returns true if at least one digit was met, /// and returns false otherwise. fn eat_float_exponent(&mut self) -> bool { + if self.first() == '_' { + return false; + } if self.first() == '-' || self.first() == '+' { self.bump(); } diff --git a/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap b/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap index e53941401..0172bd97c 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_literal_err.snap @@ -223,7 +223,10 @@ SOURCE_FILE TARGET_LIST TARGET LITERAL - FLOAT_NUMBER "1_000_.5" + INT_NUMBER "1_000_" + TARGET + LITERAL + INT_NUMBER ".5" SEMICOLON ";" WHITESPACE "\n" SELECT @@ -339,14 +342,38 @@ error[syntax-error]: trailing junk after numeric literal ╭▸ 18 │ SELECT 0x0y; ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +19 │ SELECT 100_; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +20 │ SELECT 100__000; + ╰╴ ━ error[syntax-error]: missing comma ╭▸ 21 │ SELECT _1_000.5; ╰╴ ━ error[syntax-error]: trailing junk after numeric literal ╭▸ +22 │ SELECT 1_000_.5; + ╰╴ ━ +error[syntax-error]: missing comma + ╭▸ +22 │ SELECT 1_000_.5; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ 23 │ SELECT 1_000._5; ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +24 │ SELECT 1_000.5_; + ╰╴ ━ +error[syntax-error]: Missing digits after the exponent symbol + ╭▸ +25 │ SELECT 1_000.5e_1; + ╰╴ ━ error[syntax-error]: trailing junk after positional parameter ╭▸ 26 │ SELECT $0_1; From b9d0783505bb5878586ebe0b25084f761a631942 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Thu, 21 May 2026 00:22:06 -0400 Subject: [PATCH 08/10] test --- .../tests__regression_numerology.snap | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap b/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap index da2d90b00..55d65704b 100644 --- a/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap +++ b/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap @@ -57,8 +57,32 @@ error[syntax-error]: trailing junk after numeric literal ╰╴ ━ error[syntax-error]: trailing junk after numeric literal ╭▸ +92 │ SELECT 100_; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +93 │ SELECT 100__000; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +96 │ SELECT 1_000_.5; + ╰╴ ━ +error[syntax-error]: missing comma + ╭▸ +96 │ SELECT 1_000_.5; + ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ 97 │ SELECT 1_000._5; ╰╴ ━ +error[syntax-error]: trailing junk after numeric literal + ╭▸ +98 │ SELECT 1_000.5_; + ╰╴ ━ +error[syntax-error]: Missing digits after the exponent symbol + ╭▸ +99 │ SELECT 1_000.5e_1; + ╰╴ ━ error[syntax-error]: trailing junk after positional parameter ╭▸ 101 │ PREPARE p1 AS SELECT $0_1; From a7360f9cb6a32391053bd1ef915aaeb9451c2609 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Thu, 21 May 2026 00:34:02 -0400 Subject: [PATCH 09/10] sync pg --- crates/squawk_lexer/src/lib.rs | 4 +- crates/xtask/src/sync_pg.rs | 33 ++++++- postgres/kwlist.h | 6 +- .../regression_suite/compression_pglz.sql | 10 ++ postgres/regression_suite/copy.sql | 9 ++ postgres/regression_suite/copy2.sql | 4 + postgres/regression_suite/jsonb_jsonpath.sql | 4 + postgres/regression_suite/multirangetypes.sql | 16 ++++ postgres/regression_suite/numerology.sql | 62 ++++++------ postgres/regression_suite/partition_split.sql | 15 +++ postgres/regression_suite/stats_import.sql | 95 ++++++++++++++++++- postgres/regression_suite/tsearch.sql | 8 ++ postgres/regression_suite/updatable_views.sql | 11 +++ postgres/regression_suite/window.sql | 28 ++++++ 14 files changed, 267 insertions(+), 38 deletions(-) diff --git a/crates/squawk_lexer/src/lib.rs b/crates/squawk_lexer/src/lib.rs index 9725f88f0..4c41543fc 100644 --- a/crates/squawk_lexer/src/lib.rs +++ b/crates/squawk_lexer/src/lib.rs @@ -479,7 +479,7 @@ impl Cursor<'_> { let mut has_digits = false; loop { match self.first() { - '_' if matches!(self.second(), '0'..='9') => { + '_' if self.second().is_ascii_digit() => { self.bump(); } '0'..='9' => { @@ -496,7 +496,7 @@ impl Cursor<'_> { let mut has_digits = false; loop { match self.first() { - '_' if matches!(self.second(), '0'..='9' | 'a'..='f' | 'A'..='F') => { + '_' if self.second().is_ascii_hexdigit() => { self.bump(); } '0'..='9' | 'a'..='f' | 'A'..='F' => { diff --git a/crates/xtask/src/sync_pg.rs b/crates/xtask/src/sync_pg.rs index 7223a18d9..58d61ebdb 100644 --- a/crates/xtask/src/sync_pg.rs +++ b/crates/xtask/src/sync_pg.rs @@ -35,12 +35,43 @@ const IGNORED_LINES: &[&str] = &[ "CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;", "SELECT INTO tableam_tblselectinto_heap2 USING heap2 FROM tableam_tbl_heap2;", "INSERT INTO foo DEFAULT VALUES RETURNING WITH (nonsuch AS something) *;", + "SELECT _1_000.5;", + "SELECT 123abc;", + "SELECT 0x0o;", + "SELECT 0.a;", + "SELECT 0.0a;", + "SELECT .0a;", + "SELECT 0.0e1a;", "SELECT 0.0e;", "SELECT 0.0e+a;", + "PREPARE p1 AS SELECT $1a;", + "PREPARE p1 AS SELECT $2147483648;", "SELECT 0b;", + "SELECT 1b;", + "SELECT 0b0x;", "SELECT 0o;", + "SELECT 1o;", + "SELECT 0o0x;", "SELECT 0x;", - "SELECT _1_000.5;", + "SELECT 1x;", + "SELECT 0x0y;", + "SELECT 1_000_000;", + "SELECT 1_2_3;", + "SELECT 0x1EEE_FFFF;", + "SELECT 0o2_73;", + "SELECT 0b_10_0101;", + "SELECT 1_000.000_005;", + "SELECT 1_000.;", + "SELECT .000_005;", + "SELECT 1_000.5e0_1;", + "SELECT _100;", + "SELECT 100_;", + "SELECT 100__000;", + "SELECT 1_000_.5;", + "SELECT 1_000._5;", + "SELECT 1_000.5_;", + "SELECT 1_000.5e_1;", + "PREPARE p1 AS SELECT $0_1;", "EXPLAIN (COSTS OFF) :qry;", ":qry;", "create table foo (with baz);", diff --git a/postgres/kwlist.h b/postgres/kwlist.h index 170e24ded..f6061daa7 100644 --- a/postgres/kwlist.h +++ b/postgres/kwlist.h @@ -1,7 +1,7 @@ // synced from: -// commit: 901ed9b352b41f034e17bc540725082a488fce31 -// committed at: 2026-05-08T16:44:25+07:00 -// file: https://github.com/postgres/postgres/blob/901ed9b352b41f034e17bc540725082a488fce31/src/include/parser/kwlist.h +// commit: 0392fb900eb89f52988cccd33046443c39c70d1c +// committed at: 2026-05-20T23:23:49+03:00 +// file: https://github.com/postgres/postgres/blob/0392fb900eb89f52988cccd33046443c39c70d1c/src/include/parser/kwlist.h // // update via: // cargo xtask sync-pg diff --git a/postgres/regression_suite/compression_pglz.sql b/postgres/regression_suite/compression_pglz.sql index 981301764..682790e0f 100644 --- a/postgres/regression_suite/compression_pglz.sql +++ b/postgres/regression_suite/compression_pglz.sql @@ -48,6 +48,16 @@ SELECT test_pglz_decompress('\x01ff'::bytea, 1024, true); SELECT test_pglz_decompress('\x010f01'::bytea, 1024, false); SELECT test_pglz_decompress('\x010f01'::bytea, 1024, true); +-- Corrupted compressed data. Set control bit with a valid 2-byte match +-- tag where offset exceeds output written. +SELECT test_pglz_decompress('\x011001'::bytea, 1024, false); +SELECT test_pglz_decompress('\x011001'::bytea, 1024, true); + +-- Corrupted compressed data. Set control bit with a valid 2-byte match +-- tag where offset is 0. +SELECT test_pglz_decompress('\x010300'::bytea, 1024, false); +SELECT test_pglz_decompress('\x010300'::bytea, 1024, true); + -- Clean up DROP FUNCTION test_pglz_compress; DROP FUNCTION test_pglz_decompress; diff --git a/postgres/regression_suite/copy.sql b/postgres/regression_suite/copy.sql index d676eba4e..6e21f9cb7 100644 --- a/postgres/regression_suite/copy.sql +++ b/postgres/regression_suite/copy.sql @@ -535,3 +535,12 @@ CREATE TABLE pp_510 PARTITION OF pp_2 FOR VALUES FROM (5) TO (10); INSERT INTO pp SELECT g, 10 + g FROM generate_series(1,6) g; COPY pp TO stdout(header); DROP TABLE PP; + +-- Check if COPY TO handles dropped columns in partitions. +CREATE TABLE pp_dropcol (id int, val int) PARTITION BY RANGE (id); +CREATE TABLE pp_dropcol_1 (dropme int, id int, val int); +ALTER TABLE pp_dropcol_1 DROP COLUMN dropme; +ALTER TABLE pp_dropcol ATTACH PARTITION pp_dropcol_1 FOR VALUES FROM (1) TO (10); +INSERT INTO pp_dropcol VALUES (1, 11), (2, 12); +COPY pp_dropcol TO stdout(header); +DROP TABLE pp_dropcol; diff --git a/postgres/regression_suite/copy2.sql b/postgres/regression_suite/copy2.sql index 936ad0217..33b926548 100644 --- a/postgres/regression_suite/copy2.sql +++ b/postgres/regression_suite/copy2.sql @@ -555,6 +555,10 @@ COPY t_on_error_null FROM STDIN WITH (on_error set_null); -- fail -- \N 11 13 -- \. +COPY t_on_error_null(c, a) FROM STDIN WITH (on_error set_null); -- fail +-- 11 \N +-- \. + COPY t_on_error_null FROM STDIN WITH (on_error set_null); -- fail -- ss 11 14 -- \. diff --git a/postgres/regression_suite/jsonb_jsonpath.sql b/postgres/regression_suite/jsonb_jsonpath.sql index d3a38c577..c1f4ab542 100644 --- a/postgres/regression_suite/jsonb_jsonpath.sql +++ b/postgres/regression_suite/jsonb_jsonpath.sql @@ -721,6 +721,10 @@ select jsonb_path_query('"hello world"', '$.replace("hello","bye") starts with " -- Test .split_part() select jsonb_path_query('"abc~@~def~@~ghi"', '$.split_part("~@~", 2)'); select jsonb_path_query('"abc,def,ghi,jkl"', '$.split_part(",", -2)'); +select jsonb_path_query('"a,b"', '$.split_part(",", 0)'); +select jsonb_path_query('"a,b"', '$.split_part(",", 0)', silent => true); +select jsonb_path_query('"a,b"', '$.split_part(",", 2147483648)'); +select jsonb_path_query('"a,b"', '$.split_part(",", 2147483648)', silent => true); -- Test string methods play nicely together select jsonb_path_query('"hello world"', '$.replace("hello","bye").upper()'); diff --git a/postgres/regression_suite/multirangetypes.sql b/postgres/regression_suite/multirangetypes.sql index c715d830e..5038876c0 100644 --- a/postgres/regression_suite/multirangetypes.sql +++ b/postgres/regression_suite/multirangetypes.sql @@ -743,6 +743,22 @@ drop type textrange1; reset role; drop role regress_multirange_owner; +-- +-- CREATE TYPE checks for CREATE on multirange schema +-- +create role regress_mr; +create schema mr_sch; +set role regress_mr; +create type mytype as range (subtype=int4, multirange_type_name=mr_sch.mr_type); +reset role; +grant create on schema mr_sch to regress_mr; +set role regress_mr; +create type mytype as range (subtype=int4, multirange_type_name=mr_sch.mr_type); +reset role; +drop type mytype; +drop schema mr_sch; +drop role regress_mr; + -- -- Test polymorphic type system -- diff --git a/postgres/regression_suite/numerology.sql b/postgres/regression_suite/numerology.sql index 45e5e591d..1d86a9fe6 100644 --- a/postgres/regression_suite/numerology.sql +++ b/postgres/regression_suite/numerology.sql @@ -43,40 +43,40 @@ SELECT -0x8000000000000000; SELECT -0x8000000000000001; -- error cases -SELECT 123abc; -SELECT 0x0o; -SELECT 0.a; -SELECT 0.0a; -SELECT .0a; -SELECT 0.0e1a; +-- SELECT 123abc; +-- SELECT 0x0o; +-- SELECT 0.a; +-- SELECT 0.0a; +-- SELECT .0a; +-- SELECT 0.0e1a; -- SELECT 0.0e; -- SELECT 0.0e+a; -PREPARE p1 AS SELECT $1a; -PREPARE p1 AS SELECT $2147483648; +-- PREPARE p1 AS SELECT $1a; +-- PREPARE p1 AS SELECT $2147483648; -- SELECT 0b; -SELECT 1b; -SELECT 0b0x; +-- SELECT 1b; +-- SELECT 0b0x; -- SELECT 0o; -SELECT 1o; -SELECT 0o0x; +-- SELECT 1o; +-- SELECT 0o0x; -- SELECT 0x; -SELECT 1x; -SELECT 0x0y; +-- SELECT 1x; +-- SELECT 0x0y; -- underscores -SELECT 1_000_000; -SELECT 1_2_3; -SELECT 0x1EEE_FFFF; -SELECT 0o2_73; -SELECT 0b_10_0101; +-- SELECT 1_000_000; +-- SELECT 1_2_3; +-- SELECT 0x1EEE_FFFF; +-- SELECT 0o2_73; +-- SELECT 0b_10_0101; -SELECT 1_000.000_005; -SELECT 1_000.; -SELECT .000_005; -SELECT 1_000.5e0_1; +-- SELECT 1_000.000_005; +-- SELECT 1_000.; +-- SELECT .000_005; +-- SELECT 1_000.5e0_1; DO $$ DECLARE @@ -88,17 +88,17 @@ BEGIN END $$; -- error cases -SELECT _100; -SELECT 100_; -SELECT 100__000; +-- SELECT _100; +-- SELECT 100_; +-- SELECT 100__000; -- SELECT _1_000.5; -SELECT 1_000_.5; -SELECT 1_000._5; -SELECT 1_000.5_; -SELECT 1_000.5e_1; +-- SELECT 1_000_.5; +-- SELECT 1_000._5; +-- SELECT 1_000.5_; +-- SELECT 1_000.5e_1; -PREPARE p1 AS SELECT $0_1; +-- PREPARE p1 AS SELECT $0_1; -- -- Test implicit type conversions diff --git a/postgres/regression_suite/partition_split.sql b/postgres/regression_suite/partition_split.sql index ef98e09d5..e163b7b8f 100644 --- a/postgres/regression_suite/partition_split.sql +++ b/postgres/regression_suite/partition_split.sql @@ -834,6 +834,21 @@ SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text DROP TABLE sales_range; +-- +-- Test that the explicit partition bound cannot extend outside the split +-- partition's bound when a DEFAULT partition is specified. +-- +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_51 PARTITION OF t FOR VALUES FROM (0) TO (51); +CREATE TABLE tp_51_100 PARTITION OF t FOR VALUES FROM (51) TO (100); + +-- ERROR +ALTER TABLE t SPLIT PARTITION tp_0_51 INTO + (PARTITION tp_0_51 FOR VALUES FROM (0) TO (53), + PARTITION tp_default DEFAULT); + +DROP TABLE t; + -- -- Try to SPLIT partition of another table. -- diff --git a/postgres/regression_suite/stats_import.sql b/postgres/regression_suite/stats_import.sql index 0b5fb897a..3f023428a 100644 --- a/postgres/regression_suite/stats_import.sql +++ b/postgres/regression_suite/stats_import.sql @@ -645,6 +645,60 @@ AND tablename = 'test' AND inherited = false AND attname = 'id'; +-- warn: mcv / mcf array length mismatch (more vals), mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.24::real, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.3,0.25}'::real[] + ); + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcv / mcf array length mismatch (more freqs), mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.25::real, + 'most_common_vals', '{2,1}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: most_common_vals is multi-dimensional, mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.26::real, + 'most_common_vals', '{{2,1},{3,4}}'::text, + 'most_common_freqs', '{0.3,0.25,0.05,0.04}'::real[] + ); + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + -- ok: mcv+mcf SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', @@ -1696,7 +1750,7 @@ SELECT pg_catalog.pg_restore_extended_stats( 'statistics_name', 'test_stat_clone', 'inherited', false, 'exprs', '{ "avg_width": "4", "null_frac": "0" }'::jsonb); --- wrong number of exprs +-- wrong number of exprs, too few SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', 'relname', 'test_clone', @@ -1704,6 +1758,14 @@ SELECT pg_catalog.pg_restore_extended_stats( 'statistics_name', 'test_stat_clone', 'inherited', false, 'exprs', '[ { "avg_width": "4" } ]'::jsonb); +-- wrong number of exprs, too many +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ { "avg_width": "4" }, { "avg_width": "4" }, { "avg_width": "4" } ]'::jsonb); -- incorrect type of value: should be a string or a NULL. SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', @@ -1784,6 +1846,17 @@ SELECT pg_catalog.pg_restore_extended_stats( { "most_common_freqs": "{0.5}" }, { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } ]'::jsonb); +-- exprs most_common_vals is multi-dimensional +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": "{{1,2},{3,4}}", "most_common_freqs": "{0.3,0.25,0.05,0.04}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); -- exprs most_common_vals element wrong type SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', @@ -1828,6 +1901,17 @@ SELECT pg_catalog.pg_restore_extended_stats( { "most_common_vals": "{1}", "most_common_freqs": "{BADMCF}" }, { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } ]'::jsonb); +-- exprs most_common_vals / most_common_freqs array length mismatch +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": "{1,3}", "most_common_freqs": "{0.5}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); -- exprs histogram wrong type SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', @@ -2168,6 +2252,15 @@ WHERE e.statistics_schemaname = 'stats_import' AND e.inherited = false /* \gx */; +-- bad: exprs param which is a prefix of a valid key name +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[{ "n": "-1" }]'::jsonb); + -- ok: tsvector exceptions, test just the collation exceptions CREATE STATISTICS stats_import.test_stat_tsvec ON (length(name)), (to_tsvector(name)) FROM stats_import.test; SELECT pg_catalog.pg_restore_extended_stats( diff --git a/postgres/regression_suite/tsearch.sql b/postgres/regression_suite/tsearch.sql index abfd60feb..0a93455ff 100644 --- a/postgres/regression_suite/tsearch.sql +++ b/postgres/regression_suite/tsearch.sql @@ -647,6 +647,14 @@ SELECT ts_headline('english', SELECT ts_headline('english', 'foo bar', to_tsquery('english', '')); +-- Test for large values of StartSel, StopSel and FragmentDelimiter +SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'), + 'StartSel=' || repeat('x', 32768)); +SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'), + 'StopSel=' || repeat('x', 32768)); +SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'), + 'FragmentDelimiter=' || repeat('x', 32768)); + --Rewrite sub system CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT); diff --git a/postgres/regression_suite/updatable_views.sql b/postgres/regression_suite/updatable_views.sql index 0259f4fec..3f66647be 100644 --- a/postgres/regression_suite/updatable_views.sql +++ b/postgres/regression_suite/updatable_views.sql @@ -1903,6 +1903,17 @@ select * from uv_fpo_view order by id, valid_at; delete from uv_fpo_view for portion of valid_at from '2017-01-01' to '2022-01-01' where id = '[1,1]'; select * from uv_fpo_view order by id, valid_at; +-- UPDATE/DELETE FOR PORTION fails if the column is not updatable +-- (e.g. a computed expression, not a base column): +create view uv_fpo_view_nonupd as + select id, '[1,20]'::int4range as valid_at, b + from uv_fpo_tab; +-- Updating fails: +update uv_fpo_view_nonupd for portion of valid_at from 1 to 10 set b = 2; +-- Deleting fails: +delete from uv_fpo_view_nonupd for portion of valid_at from 1 to 10; +drop view uv_fpo_view_nonupd; + -- Test whole-row references to the view create table uv_iocu_tab (a int unique, b text); create view uv_iocu_view as diff --git a/postgres/regression_suite/window.sql b/postgres/regression_suite/window.sql index 999c7aecc..3dcb29f5d 100644 --- a/postgres/regression_suite/window.sql +++ b/postgres/regression_suite/window.sql @@ -2157,5 +2157,33 @@ SELECT x, FROM generate_series(1,5) g(x) WINDOW w AS (ORDER BY x ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING); +-- volatile arguments cannot use the IGNORE NULLS nullness cache +CREATE TEMPORARY SEQUENCE null_treatment_seq; +CREATE FUNCTION pg_temp.volatile_null(i int) RETURNS int +LANGUAGE sql VOLATILE AS +$$ + SELECT CASE WHEN nextval('null_treatment_seq') % 2 = 0 THEN i ELSE NULL END; +$$; + +SELECT x, + first_value(pg_temp.volatile_null(x)) IGNORE NULLS + OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) +FROM generate_series(1,5) g(x); +SELECT last_value FROM null_treatment_seq; + +ALTER SEQUENCE null_treatment_seq RESTART WITH 1; +SELECT x, + lead(pg_temp.volatile_null(x), 1) IGNORE NULLS OVER (ORDER BY x) +FROM generate_series(1,5) g(x); +SELECT last_value FROM null_treatment_seq; + +ALTER SEQUENCE null_treatment_seq RESTART WITH 1; +SELECT x, + first_value((SELECT CASE WHEN nextval('null_treatment_seq') % 2 = 0 + THEN x ELSE NULL END)) IGNORE NULLS + OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) +FROM generate_series(1,5) g(x); +SELECT last_value FROM null_treatment_seq; + --cleanup DROP TABLE planets CASCADE; From 2059a62a735afb8cf499683fcd732d56c7503487 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Thu, 21 May 2026 00:34:53 -0400 Subject: [PATCH 10/10] fix --- .../tests__regression_numerology.snap | 86 +------------------ 1 file changed, 1 insertion(+), 85 deletions(-) diff --git a/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap b/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap index 55d65704b..5a3a21e39 100644 --- a/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap +++ b/crates/squawk_parser/tests/snapshots/tests__regression_numerology.snap @@ -2,88 +2,4 @@ source: crates/squawk_parser/tests/tests.rs input_file: postgres/regression_suite/numerology.sql --- ---- -error[syntax-error]: trailing junk after numeric literal - ╭▸ -46 │ SELECT 123abc; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -47 │ SELECT 0x0o; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -48 │ SELECT 0.a; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -49 │ SELECT 0.0a; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -50 │ SELECT .0a; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -51 │ SELECT 0.0e1a; - ╰╴ ━ -error[syntax-error]: trailing junk after positional parameter - ╭▸ -54 │ PREPARE p1 AS SELECT $1a; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -58 │ SELECT 1b; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -59 │ SELECT 0b0x; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -62 │ SELECT 1o; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -63 │ SELECT 0o0x; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -66 │ SELECT 1x; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -67 │ SELECT 0x0y; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -92 │ SELECT 100_; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -93 │ SELECT 100__000; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -96 │ SELECT 1_000_.5; - ╰╴ ━ -error[syntax-error]: missing comma - ╭▸ -96 │ SELECT 1_000_.5; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -97 │ SELECT 1_000._5; - ╰╴ ━ -error[syntax-error]: trailing junk after numeric literal - ╭▸ -98 │ SELECT 1_000.5_; - ╰╴ ━ -error[syntax-error]: Missing digits after the exponent symbol - ╭▸ -99 │ SELECT 1_000.5e_1; - ╰╴ ━ -error[syntax-error]: trailing junk after positional parameter - ╭▸ -101 │ PREPARE p1 AS SELECT $0_1; - ╰╴ ━ +