infiniflow / infiniflow/infinity
[Bug]: Python SDKs lowercase output expressions, silently corrupting string literals
- Dominant language
- C++
- Stars
- 4.7k
- Forks
- 445
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 7
Description
## Problem
Both Python SDKs (thrift and embedded) lowercase every `output()` column string **before** parsing it:
```python
table.output(["'ACTIVE'"]) # silently selects 'active'
```
The lowering is meant to make the special output tokens (`*`, `_row_id`, `_score`, `_distance`, ...) case-insensitive, but it rewrites the whole expression string, including string literals inside it. Any literal with uppercase characters is corrupted before the server ever sees it - and once `json_extract(...)` parses through the SDKs (currently blocked by #3463), a call like `json_extract(data, '$.UserName')` would silently query the key `$.username` and return NULL.
The HTTP API is not affected: it forwards output strings to the server, which parses them case-correctly. So the same query returns different data depending on which client you use.
## Root cause
`python/infinity_sdk/infinity/remote_thrift/query_builder.py` and `python/infinity_embedded/local_infinity/query_builder.py`, `output()`:
```python
for column in columns:
if isinstance(column, str):
column = column.lower() # rewrites the expression itself
match column:
...
case _:
... maybe_parse(column) ... # parses the lowered string
```
## Fix
Match the special tokens against a lowercased *copy* and parse the original string:
```python
key = column.lower() if isinstance(column, str) else column
match key:
...
case _:
... maybe_parse(column) ... # original case preserved
```
Plain identifiers are still lowercased later by the `exp.Column` arm (`alias_or_name.lower()`), so column-name handling is unchanged; only string literals keep their case.
## Testing
- Reproduced on current main (eca7266), sqlglot 30.18.0: before the fix `output(["'ACTIVE'"])` built a constant expression holding `'active'` in both SDKs; after the fix it holds `'ACTIVE'`. `_SCORE` still maps to the `score` function, `MyCol` still normalizes to `mycol`, and `*` still selects all.
- Added regression test `test_output_preserves_string_literal_case` in `python/test_pysdk/test_condition.py` covering the thrift builder directly and the embedded builder in embedded mode.
- Full pysdk suite not run locally; it needs a running server / built embedded engine.
Contributor guide
Research direction
Inspect output() in python/infinity_sdk/infinity/remote_thrift/query_builder.py and python/infinity_embedded/local_infinity/query_builder.py, then read test_output_preserves_string_literal_case in python/test_pysdk/test_condition.py. Verify that special tokens remain case-insensitive while string literals retain their original case, identifiers still normalize, and the existing regression test passes in both builders.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- Half a day
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100