diff --git a/pyiceberg/expressions/parser.py b/pyiceberg/expressions/parser.py index d813bcaa07..73da05c2ec 100644 --- a/pyiceberg/expressions/parser.py +++ b/pyiceberg/expressions/parser.py @@ -298,6 +298,10 @@ def handle_or(result: ParseResults) -> Or: ], ).set_name("expr") +# pyparsing expands tabs in the input by default, which would rewrite a tab inside a string literal +# into spaces, and the number of spaces would depend on where the literal sits in the expression. +boolean_expression.parse_with_tabs() + def parse(expr: str) -> BooleanExpression: """Parse a boolean expression.""" diff --git a/tests/expressions/test_parser.py b/tests/expressions/test_parser.py index 581ee90b72..88e6b69b0f 100644 --- a/tests/expressions/test_parser.py +++ b/tests/expressions/test_parser.py @@ -294,3 +294,14 @@ def test_boolean_as_operand(expression: str, expected: BooleanExpression) -> Non def test_boolean_as_literal_is_unchanged() -> None: assert parser.parse("foo = true") == EqualTo(Reference("foo"), literal(True)) assert parser.parse("foo in (true, false)") == In(Reference("foo"), {literal(True), literal(False)}) + + +def test_literal_with_tab_is_preserved() -> None: + assert EqualTo("foo", "a\tb") == parser.parse("foo = 'a\tb'") + + +def test_literal_with_tab_does_not_depend_on_its_position() -> None: + one_char_column = parser.parse("a = 'x\ty'") + two_char_column = parser.parse("ab = 'x\ty'") + + assert one_char_column.literal.value == two_char_column.literal.value