From 3d096ee2068834894bbfad8731e0f337ab20c36b Mon Sep 17 00:00:00 2001 From: adarshsm Date: Sun, 11 Oct 2026 15:55:25 +0530 Subject: [PATCH] Lex a minus directly after an operand as an operator (fixes #370) The number regexes accepted an optional leading minus anywhere, so in `1-1` or `a-1` the minus was consumed by the second number and the binary operator was lost (`1`, `-1` instead of `1`, `-`, `1`). As a result `select a-1 from foo` was not grouped as an Operation. A leading minus now only belongs to the number if it doesn't directly follow a name, number, closing bracket or quoted string/name. Unary minus after an operator, punctuation or whitespace (`x = -1`, `values(-1)`, `a -1`) is lexed as before. Co-Authored-By: Claude Opus 5.5 --- sqlparse/keywords.py | 14 ++++++++++---- tests/test_grouping.py | 7 +++++++ tests/test_tokenize.py | 10 ++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/sqlparse/keywords.py b/sqlparse/keywords.py index dd6e5d15..c761fe31 100644 --- a/sqlparse/keywords.py +++ b/sqlparse/keywords.py @@ -158,11 +158,17 @@ def find_delimited_spans(text): # because `re.match` doesn't work with look-behind regexp feature (r'(?<=\.)[A-ZÀ-Ü]\w*', tokens.Name), # .'Name' (r'[A-ZÀ-Ü]\w*(?=\()', tokens.Name), # side effect: change kw to func - (r'-?0x[\dA-F]+', tokens.Number.Hexadecimal), - (r'-?\d+(\.\d+)?E-?\d+', tokens.Number.Float), - (r'(?![_A-ZÀ-Ü])-?(\d+(\.\d*)|\.\d+)(?![_A-ZÀ-Ü])', + # A leading minus is only part of the number if it can't be a binary + # operator, i.e. if it doesn't directly follow an operand (a name, a + # number, a closing bracket or a quoted string or name), so that + # ``1-1`` is lexed as ``1``, ``-``, ``1``. See issue #370. + (r"(?:(?